自己動手開發(fā)最好的代碼編輯器
這一年來我花了很多的時間在寫一個代碼編輯器。大部分時間都是在實現(xiàn)各種各樣的功能,其中也遇到了不少的問題?,F(xiàn)在把實現(xiàn)這個編輯控件的一些問題的解決方法寫出來,以供參考。這里說明下,我用的是MFC,當然了,沒有用現(xiàn)成的控件,而是直接從CWnd繼承來實現(xiàn)自己的編輯控件。
先給大家弄個效果圖吧,你可以在這里CuteC Editor下載,歡迎大家提出意見。

問題1:如何讓控件接受所有的按鍵和漢字。
問題2:如何計算光標的位置。
問題3:如何存儲編輯控件的文本內容。
問題4:如何實現(xiàn)關鍵字高亮。
問題5:如何實現(xiàn)自動換行。
問題6:如何解析腳本。呵呵,我自己寫了個C語言解釋器,那它來用還是很不錯的。
一. 如何讓控件接受所有的按鍵和漢字。
讓CWnd接收所有的按鍵做法很簡單,只需響應WM_GETDLGCODE,代碼如下:
- afx_msg UINT OnGetDlgCode();
- ...
- ON_WM_GETDLGCODE()
- ...
- UINT CLEditWnd::OnGetDlgCode(){
- return DLGC_WANTALLKEYS;
- }
接收漢字就比較麻煩了,必須響應WM_IME_CHAR消息。我得做法如下,不知有沒有更簡單的方法。
1. 重新設置窗體的WND_PROC函數(shù)。在這個函數(shù)中獲取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回調函數(shù),并把返回值賦給LEditWndProcOld。而在LEditWndProcNew函數(shù)中,把WM_IME_CHAR消息通過自定義消費WM_LEDIT_ZW發(fā)回CLEditWnd窗體。漢字就保存在wParam參數(shù)中??梢赃@樣獲得: char hz[3] = { wParam>>8, wParam, 0 };
二. 如何計算光標的位置。
這個問題看似簡單,但其實在程序的開發(fā)過程中是最難調試的。首先我們要明確以下問題:
1. 知道光標所在的行的位置,要計算出他在界面中的像素位置。
2. 知道鼠標點擊的位置,要把它轉化成字符串中所對應的位置。
Windows提供GetTextExtent來計算字符串顯示的寬度。我們知道調用這個函數(shù)就可以解決上述的問題了。但是當你這么去做的是后,你才知道效率有多低,當你在選擇內容移動鼠標時,要及時的計算光標的位置,你就知道效率跟不上了。想了很久,終于想出了個辦法:
在創(chuàng)建好控件后,首先調用GetTextExtent來計算所有英文字符和漢字的寬度,接下來我們就不直接調用GetTextExtent這個函數(shù)了。而是直接根據(jù)已經(jīng)算到的字符寬度來計算字符串的寬度。效率得到大大的提高。我這里給出了我的相關代碼。
- 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;
- }
nCharWidth數(shù)組中的信息足以計算任何字符串的顯示寬度。唯一不足的是在更換字體的時候,我們必須跟換這個數(shù)組的內容。
三(1). 如何存儲編輯控件的文本內容
在打開文件,編輯文檔時,我們必須在內存中存儲這個文檔的最新內容,并且實時的更新到界面上。在MFC上,沒有什么比CStringArray更合適的了,雖然有人說CStringArray會內存泄露,但我測試下來沒發(fā)現(xiàn)這個問題,總覺得是說這話的人自己的代碼沒寫好造成的。CStringArray在很多行數(shù)據(jù)的數(shù)據(jù)估計插入的效率不高,但對于打文件的處理,我們分開來處理的。CStringArray提供了數(shù)組和字符串的功能,所以對字符串的操作就方便多了。唯一的不足是,我們必須預先處理文件,把文件的每行保存到CStringArray中。在大文件的讀取中,這會浪費一定的時間。
三(2). 另一個重要的問題就是大文件的處理。對于大文件,我做了特殊的處理。
1. 采用內存映射文件掃描整個文件,提取出行信息。
2. 采用分塊處理來操作整個文件,使控件中保存的數(shù)據(jù)僅僅是文件的一個塊。
3. 當大文件被修改,當塊被切換時,這個塊數(shù)據(jù)必須保存在內存中,或者必須保存到另一個中間文件。而對于沒有被修改的塊,則不需做任何處理。
4. 在保存大文件時,必須根據(jù)每塊的信息重新寫入文件。
- * 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; //臟數(shù)據(jù),用來保存被修改過的塊數(shù)據(jù),如果為NULL,則表示該塊沒被修改過。
- public:
- CBlockNode & operator = ( CBlockNode &src );
- };
四. 如何實現(xiàn)關鍵字高亮。
1. 關鍵字怎么保存在配置文件中每個人有每個人的做法。關鍵問題在于如何快速的查找字符串中存在這個關鍵字。
2. 當關鍵字很多的時候,查找的效率就有講究了。
3. 如何在內存中保持信息,在界面中顯示。
我們倒過來講:
3. 首先在界面上顯示一行文字很簡單,調用TextOut就可以了。最好不要用DrawText,效率比TextOut低很多。
為了對每行顯示的時候提供顏色信息,在內存中必須保持一個足夠長的數(shù)組,來保持每個字符對應的顏色。而在顯示的時候,一個一個字符先SetTextColor再TextOut就可了。然而這樣效率不是很高,好的辦法是,對相同的顏色的詞一次性的重繪出來,盡量減少TextOut的調用。所以我又加了一個數(shù)組保存了每個關鍵字的長度。
這里有個問題,不能為穩(wěn)定的每行都保存這樣的數(shù)組,不然內存空間占用會很大。而是在繪制行的
2. 關鍵字很多的時候,我們必須對每個詞一一去判斷該詞是否在關鍵字中。所以hash表是比較合適的選擇了。這里不多講。
1. 要提取出一個字符串中的詞,然后根據(jù)詞再去判斷是否是關鍵字。所以就涉及到字符串的斷詞功能。例如一個字符串:
- This is a test line string , 哈哈 :).
- 我們必須提取出:
- This
- -
- is
- -
- a
- -
- line
- -
- string
- -
- ,
- -
- 哈哈
- :
- )
- .
其中 - 表示空格。然后再到關鍵字表中匹配,判斷該詞是否是關鍵字。如果是關鍵字,修改顏色數(shù)組的顏色信息,供界面使用。
五. 如何實現(xiàn)自動換行。
在顯示行的時候,我們不是直接那保存在內存的行數(shù)據(jù)就直接TextOut出來,而是要經(jīng)過幾個步驟來處理改行數(shù)據(jù)。
1. 處理Tab鍵(0x09),當我們碰到0x09時,必須將它替換成空格,當然沒個Tab在不同的位置用不同的空格補全,保證補全后能被TAB_LEN整除。這樣就能得到去除TAB后的字符串。
2. 統(tǒng)計第1步得到的字符串,自動換行后,將每行保存為CStringArray,然后在界面中顯示。
3. 添加自動換行功能,對光標的計算會有影響,所以在將界面像素點轉成光標位置時,必須要統(tǒng)計當前界面的每行的子行數(shù)(自動換行后所得的行數(shù))。然后才能確定在第幾行。所以計算起來比較麻煩。
原文:http://www.cnblogs.com/linxr/archive/2011/10/30/2229256.html
【編輯推薦】