zoukankan      html  css  js  c++  java
  • 一个代码编辑器的实现

       这一年来我花了很多的时间在写一个代码编辑器。大部分时间都是在实现各种各样的功能,其中也遇到了不少的问题。现在把实现这个编辑控件的一些问题的解决方法写出来,以供参考。这里说明下,我用的是MFC,当然了,没有用现成的控件,而是直接从CWnd继承来实现自己的编辑控件。

    先给大家弄个效果图吧,你可以在这里CuteC Editor下载,欢迎大家提出意见。

     

    问题1:如何让控件接受所有的按键和汉字。
    问题2:如何计算光标的位置。
    问题3:如何存储编辑控件的文本内容。
    问题4:如何实现关键字高亮。
    问题5:如何实现自动换行。
    问题6:如何解析脚本。呵呵,我自己写了个C语言解释器,那它来用还是很不错的。

    一. 如何让控件接受所有的按键和汉字。
        让CWnd接收所有的按键做法很简单,只需响应WM_GETDLGCODE,代码如下:
           afx_msg UINT OnGetDlgCode();
           ...
           ON_WM_GETDLGCODE()
           ...
           UINT CLEditWnd::OnGetDlgCode(){
               return DLGC_WANTALLKEYS;
           }
      
        接收汉字就比较麻烦了,必须响应WM_IME_CHAR消息。我得做法如下,不知有没有更简单的方法。
        1. 重新设置窗体的WND_PROC函数。在这个函数中获取WM_IME_CHAR消息,并通过自定义消费返回我们的CWnd窗体。
           WNDPROC LEditWndProcOld;
           LRESULT LEditWndProcNew(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
               CWnd *pWnd = CWnd::FromHandlePermanent( hWnd );
               if(uMsg==WM_IME_CHAR){ 
                   pWnd->PostMessage(WM_LEDIT_ZW, wParam, lParam );
                   return   0;
               }
               return CallWindowProc( LEditWndProcOld, hWnd, uMsg, wParam, lParam);
           }
           ...
           void CLEditWnd::PreSubclassWindow(){
               LEditWndProcOld = (WNDPROC)SetWindowLong(this->GetSafeHwnd(), GWL_WNDPROC,  (LONG)LEditWndProcNew);
               CWnd::PreSubclassWindow();
           }
         2.响应WM_LEDIT_ZW自定义消息,获取汉字内容。
        
         在PreSubclassWindow设置了LEditWndProcNew回调函数,并把返回值赋给LEditWndProcOld。而在LEditWndProcNew函数中,把WM_IME_CHAR消息通过自定义消费WM_LEDIT_ZW发回CLEditWnd窗体。汉字就保存在wParam参数中。可以这样获得: char hz[3] = { wParam>>8, wParam, 0 };


    二. 如何计算光标的位置。
        这个问题看似简单,但其实在程序的开发过程中是最难调试的。首先我们要明确以下问题:
     1. 知道光标所在的行的位置,要计算出他在界面中的像素位置。
     2. 知道鼠标点击的位置,要把它转化成字符串中所对应的位置。
        Windows提供GetTextExtent来计算字符串显示的宽度。我们知道调用这个函数就可以解决上述的问题了。但是当你这么去做的是后,你才知道效率有多低,当你在选择内容移动鼠标时,要及时的计算光标的位置,你就知道效率跟不上了。想了很久,终于想出了个办法:
         在创建好控件后,首先调用GetTextExtent来计算所有英文字符和汉字的宽度,接下来我们就不直接调用GetTextExtent这个函数了。而是直接根据已经算到的字符宽度来计算字符串的宽度。效率得到大大的提高。我这里给出了我的相关代码。
            char data[2];
            m_cText.nCharWidth[0] = 0;
            for( i=1; i<256; i++ ){
                data[0] = i;
                data[1] = 0;
                m_cText.nCharWidth[i] = (unsigned char)pDC->GetTextExtent( data ).cx;
            }
            m_cText.nCharWidth[256] = (unsigned char)pDC->GetTextExtent( "中" ).cx;

            nCharWidth数组中的信息足以计算任何字符串的显示宽度。唯一不足的是在更换字体的时候,我们必须跟换这个数组的内容。


    三(1). 如何存储编辑控件的文本内容
        在打开文件,编辑文档时,我们必须在内存中存储这个文档的最新内容,并且实时的更新到界面上。在MFC上,没有什么比CStringArray更合适的了,虽然有人说CStringArray会内存泄露,但我测试下来没发现这个问题,总觉得是说这话的人自己的代码没写好造成的。CStringArray在很多行数据的数据估计插入的效率不高,但对于打文件的处理,我们分开来处理的。CStringArray提供了数组和字符串的功能,所以对字符串的操作就方便多了。唯一的不足是,我们必须预先处理文件,把文件的每行保存到CStringArray中。在大文件的读取中,这会浪费一定的时间。

    三(2). 另一个重要的问题就是大文件的处理。对于大文件,我做了特殊的处理。
     1. 采用内存映射文件扫描整个文件,提取出行信息。
     2. 采用分块处理来操作整个文件,使控件中保存的数据仅仅是文件的一个块。
     3. 当大文件被修改,当块被切换时,这个块数据必须保存在内存中,或者必须保存到另一个中间文件。而对于没有被修改的块,则不需做任何处理。
     4. 在保存大文件时,必须根据每块的信息重新写入文件。
        * Block 01
        * Block 02
        * Block 03
        * ...
        * Block n
        每个Block我们必须保存它相关的信息。我定义了一个类,声明如下:
               class CBlockNode
               {
               public:
                   CBlockNode();
                   ~CBlockNode();
               public:
                   __int64 lBlkBegin;        //块开始位置,在文件中的开始位置
                   LONG lBlkSize;            //块大小
                   LONG lLineTop;            //开始行
                   LONG lLineLow;            //结束行
                   CString sLeftString;      //该块的剩余行, 应为连个块之间的分割处,有可能会把一行分隔开,这里保存最后一行的前半部分。
                                    //必须做特殊的处理,以保证两块的分割处就是换行符。则可以保证改字段为空。
                   char *pDirtyCtx;          //脏数据,用来保存被修改过的块数据,如果为NULL,则表示该块没被修改过。

               public:
                   CBlockNode & operator = ( CBlockNode &src );
               };


    四. 如何实现关键字高亮。
        1. 关键字怎么保存在配置文件中每个人有每个人的做法。关键问题在于如何快速的查找字符串中存在这个关键字。
     2. 当关键字很多的时候,查找的效率就有讲究了。
     3. 如何在内存中保持信息,在界面中显示。
     我们倒过来讲:
     3. 首先在界面上显示一行文字很简单,调用TextOut就可以了。最好不要用DrawText,效率比TextOut低很多。
        为了对每行显示的时候提供颜色信息,在内存中必须保持一个足够长的数组,来保持每个字符对应的颜色。而在显示的时候,一个一个字符先SetTextColor再TextOut就可了。然而这样效率不是很高,好的办法是,对相同的颜色的词一次性的重绘出来,尽量减少TextOut的调用。所以我又加了一个数组保存了每个关键字的长度。
        这里有个问题,不能为稳定的每行都保存这样的数组,不然内存空间占用会很大。而是在绘制行的
     2. 关键字很多的时候,我们必须对每个词一一去判断该词是否在关键字中。所以hash表是比较合适的选择了。这里不多讲。
     1. 要提取出一个字符串中的词,然后根据词再去判断是否是关键字。所以就涉及到字符串的断词功能。例如一个字符串:
          This is a test line string , 哈哈 :).
        我们必须提取出:
               This
         -
         is
         -
         a
         -
         line
         -
         string
         -
         ,
         -
         哈哈
         :
         )
         .
      其中 - 表示空格。然后再到关键字表中匹配,判断该词是否是关键字。如果是关键字,修改颜色数组的颜色信息,供界面使用。


    五. 如何实现自动换行。
        在显示行的时候,我们不是直接那保存在内存的行数据就直接TextOut出来,而是要经过几个步骤来处理改行数据。
     1. 处理Tab键(0x09),当我们碰到0x09时,必须将它替换成空格,当然没个Tab在不同的位置用不同的空格补全,保证补全后能被TAB_LEN整除。这样就能得到去除TAB后的字符串。
     2. 统计第1步得到的字符串,自动换行后,将每行保存为CStringArray,然后在界面中显示。
     3. 添加自动换行功能,对光标的计算会有影响,所以在将界面像素点转成光标位置时,必须要统计当前界面的每行的子行数(自动换行后所得的行数)。然后才能确定在第几行。所以计算起来比较麻烦。

  • 相关阅读:
    深入Android 【一】 —— 序及开篇
    Android中ContentProvider和ContentResolver使用入门
    深入Android 【六】 —— 界面构造
    The service cannot be activated because it does not support ASP.NET compatibility. ASP.NET compatibility is enabled for this application. Turn off ASP.NET compatibility mode in the web.config or add the AspNetCompatibilityRequirements attribute to the ser
    Dynamic Business代码片段总结
    对文件的BuildAction以content,resource两种方式的读取
    paraview 3.12.0 windows下编译成功 小记
    百度网盘PanDownload使用Aria2满速下载
    netdata的安装与使用
    用PS给证件照排版教程
  • 原文地址:https://www.cnblogs.com/linxr/p/2229256.html
Copyright © 2011-2022 走看看