📄 lion-tutorial35.htm
字号:
<html><head><title>Iczelion's Win32 Assembly Tutorial 35: RichEdit Control: Syntax Hilighting</title><meta http-equiv="Content-Type" content="text/html; charset="></head><body bgcolor="#FFFFFF"><h1 align="center"><font face="Tahoma" color="#0000FF">Tutorial 35: RichEdit Control: Syntax Hilighting</font></h1><p align="left"><font face="Tahoma" size="-1">Before reading this tutorial, let me warn you that it's a complicated subject: not suited for a beginner. This is the last in the richedit control tutorials.</font></p><p align="left"><font face="Tahoma" size="-1">Download <a href="files/tut35.zip">the example</a>.</font></p><h3 align="left"><font face="Times New Roman, Times, serif" color="#0000FF">Theory</font></h3><p align="left"><font face="Tahoma" size="-1">Syntax hilighting is a subject of hot debate for those writing text editors. The best method (in my opinion) is to code a custom edit control and this is the approach taken by lots of commercial softwares. However, for those of us who don't have time for coding such control, the next best thing is to adapt the existing control to make it suit our need.</font></p><p align="left"><font face="Tahoma" size="-1">Let us take a look at what RichEdit control provides to help us in implementing syntax hilighting. I should state at this moment that the following method is not the "correct" path: I just want to show you the pitfall that many fall for. RichEdit control provides <font color="#006666"> <b>EM_SETCHARFORMAT</b></font> message that you can use to change the color of the text. At first glance, this message seems to be the perfect solution (I know because I was one of the victim). However, a closer examination will show you several things that are undesirable:</font></p><ul> <li><font face="Tahoma" size="-1"><b><font color="#006666">EM_SETCHARFORMAT</font></b> only works for a text currently in selection or all text in the control. If you want to change the text color (hilight) a certain word, you must first select it.</font></li> <li><font face="Tahoma" size="-1"><b><font color="#006666">EM_SETCHARFORMAT</font></b> is very slow</font></li> <li><font face="Tahoma" size="-1">It has a problem with the caret position in the richedit control</font></li></ul><p><font face="Tahoma" size="-1">With the above discussion, you can see that using <font color="#006666"> <b>EM_SETCHARFORMAT</b></font> is a wrong choice. I'll show you the "relatively correct" choice.</font></p><p><font face="Tahoma" size="-1">The method I currently use is "syntax hilighting just-in-time". I'll hilight only the visible portion of text. Thus the speed of the hilighting will not be related to the size of the file at all. No matter how large the file, only a small portion of it is visible at one time.</font></p><p><font face="Tahoma" size="-1">How to do that? The answer is simple: </font></p><ol> <li><font face="Tahoma" size="-1">subclass the richedit control and handle <font color="#006666"><b>WM_PAINT</b></font> message within your own window procedure</font></li> <li><font face="Tahoma" size="-1">When it receives <font color="#006666"><b>WM_PAINT</b></font> message, it calls the original window procedure of the richedit control to let it update the screen as usual.</font></li> <li><font face="Tahoma" size="-1">After that, we overwrite the words to be hilighted with different color</font></li></ol><p><font face="Tahoma" size="-1">Of course, the road is not that easy: there are still a couple of minor things to fix but the above method works quite nicely. The display speed is very satisfactory.</font></p><p><font face="Tahoma" size="-1">Now let's concentrate on the detail. The subclassing process is simple and doesn't require much attention. The really complicated part is when we have to find a fast way of searching for the words to be hilighted. This is further complicated by the need <font color="#006666"><b>not</b></font> to hilight any word within a comment block. </font></p><p><font face="Tahoma" size="-1">The method I use may not be the best but it works ok. I'm sure you can find a faster way. Anyway, here it is:</font></p><ul> <li><font face="Tahoma" size="-1">I create a 256 dword array, initialized to 0. Each dword corresponds to a possible ASCII character,named <font color="#0000CC"><b>ASMSyntaxArray</b></font>. For example, the 21th dword represents the ascii 20h (space). I use them as a fast lookup table: For example, if I have the word "include", I'll extract the first character (i) from the word and look up the dword at the corresponding index. If that dword is 0, I know immediately that there is no words to be hilighted starting with "i". If the dword is non-zero, it contains the pointer to the linked list of the <font color="#006666"><b>WORDINFO</b></font> structure which contains the information about the word to be hilighted.</font></li> <li><font face="Tahoma" size="-1">I read the words to be hilighted and create a <font color="#006666"><b>WORDINFO</b></font> structure for each of them.</font></li></ul><pre><font face="Tahoma"><b> WORDINFO struct WordLen dd ? ; the length of the word: used as a quick comparison pszWord dd ? ; pointer to the word pColor dd ? ; point to the dword that contains the color used to hilite the word NextLink dd ? ; point to the next WORDINFO structure WORDINFO ends </b></font></pre><blockquote> <p><font face="Tahoma" size="-1">As you can see, I use the length of the word as the second quick comparison. If the first character of the word matches, we next compare its length to the available words. Each dword in <font color="#0000CC"><b>ASMSyntaxArray</b></font> contains a pointer to the head of the associated <font color="#006666"><b>WORDINFO</b></font> array. For example, the dword that represents the character "i" will contain the pointer to the linked list of the words that begin with "i". <font color="#990099"> <b>pColor</b></font> member points to the dword that contains the color value used to hilight the word. <font color="#990099"><b>pszWord</b></font> points to the word to be hilighted, in lowercase.</font></p></blockquote><ul> <li><font face="Tahoma" size="-1">The memory for the linked list is allocated from the default heap so it's fast and easy to clean up, ie, no cleaning up required at all.</font></li></ul><p><font face="Tahoma" size="-1">The word list is stored in a file named "wordfile.txt" and I access it with <font color="#000099"><b>GetPrivateProfileString</b></font> APIs. I provide as many as 10 different syntax coloring, starting from C1 to C10. The color array is named <font color="#006666"><b>ASMColorArray</b></font>. pColor member of each <font color="#006666"><b>WORDINFO</b></font> structure points to one of the dwords in <font color="#006666"><b>ASMColorArray</b></font>. Thus it is easy to change the syntax coloring on the fly: you just change the dword in <font color="#006666"><b>ASMColorArray</b></font> and all words using that color will use the new color immediately. </font></p><h3><font face="Times New Roman, Times, serif" color="#0000CC">Example</font></h3><pre><font face="Tahoma"><b>.386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\comdlg32.incinclude \masm32\include\gdi32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\gdi32.libincludelib \masm32\lib\comdlg32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libWinMain proto :DWORD,:DWORD,:DWORD,:DWORDWORDINFO struct WordLen dd ? ; the length of the word: used as a quick comparison pszWord dd ? ; pointer to the word pColor dd ? ; point to the dword that contains the color used to hilite the word NextLink dd ? ; point to the next WORDINFO structureWORDINFO ends.constIDR_MAINMENU equ 101IDM_OPEN equ 40001IDM_SAVE equ 40002IDM_CLOSE equ 40003IDM_SAVEAS equ 40004IDM_EXIT equ 40005IDM_COPY equ 40006IDM_CUT equ 40007IDM_PASTE equ 40008IDM_DELETE equ 40009IDM_SELECTALL equ 40010IDM_OPTION equ 40011IDM_UNDO equ 40012IDM_REDO equ 40013IDD_OPTIONDLG equ 101IDC_BACKCOLORBOX equ 1000IDC_TEXTCOLORBOX equ 1001IDR_MAINACCEL equ 105IDD_FINDDLG equ 102IDD_GOTODLG equ 103IDD_REPLACEDLG equ 104IDC_FINDEDIT equ 1000IDC_MATCHCASE equ 1001IDC_REPLACEEDIT equ 1001IDC_WHOLEWORD equ 1002IDC_DOWN equ 1003IDC_UP equ 1004IDC_LINENO equ 1005IDM_FIND equ 40014IDM_FINDNEXT equ 40015IDM_REPLACE equ 40016IDM_GOTOLINE equ 40017IDM_FINDPREV equ 40018RichEditID equ 300.dataClassName db "IczEditClass",0AppName db "IczEdit version 3.0",0RichEditDLL db "riched20.dll",0RichEditClass db "RichEdit20A",0NoRichEdit db "Cannot find riched20.dll",0ASMFilterString db "ASM Source code (*.asm)",0,"*.asm",0 db "All Files (*.*)",0,"*.*",0,0OpenFileFail db "Cannot open the file",0WannaSave db "The data in the control is modified. Want to save it?",0FileOpened dd FALSEBackgroundColor dd 0FFFFFFh ; default to whiteTextColor dd 0 ; default to blackWordFileName db "\wordfile.txt",0ASMSection db "ASSEMBLY",0C1Key db "C1",0C2Key db "C2",0C3Key db "C3",0C4Key db "C4",0C5Key db "C5",0C6Key db "C6",0C7Key db "C7",0C8Key db "C8",0C9Key db "C9",0C10Key db "C10",0ZeroString db 0ASMColorArray dd 0FF0000h,0805F50h,0FFh,666F00h,44F0h,5F8754h,4 dup(0FF0000h)CommentColor dd 808000h.data?hInstance dd ?hRichEdit dd ?hwndRichEdit dd ?FileName db 256 dup(?)AlternateFileName db 256 dup(?)CustomColors dd 16 dup(?)FindBuffer db 256 dup(?)ReplaceBuffer db 256 dup(?)uFlags dd ?findtext FINDTEXTEX <>ASMSyntaxArray dd 256 dup(?)hSearch dd ? ; handle to the search/replace dialog boxhAccel dd ?hMainHeap dd ? ; heap handleOldWndProc dd ?RichEditVersion dd ?.codestart: mov byte ptr [FindBuffer],0 mov byte ptr [ReplaceBuffer],0 invoke GetModuleHandle, NULL mov hInstance,eax invoke LoadLibrary,addr RichEditDLL .if eax!=0 mov hRichEdit,eax invoke GetProcessHeap mov hMainHeap,eax call FillHiliteInfo invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT invoke FreeLibrary,hRichEdit .else invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR .endif invoke ExitProcess,eax WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:DWORD mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,IDR_MAINMENU mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd invoke LoadAccelerators,hInstance,IDR_MAINACCEL mov hAccel,eax .while TRUE invoke GetMessage, ADDR msg,0,0,0 .break .if (!eax) invoke IsDialogMessage,hSearch,addr msg .if eax==FALSE invoke TranslateAccelerator,hwnd,hAccel,addr msg .if eax==0 invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endif .endif .endw mov eax,msg.wParam retWinMain endpStreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0 xor eax,1 retStreamInProc endpStreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0 xor eax,1 retStreamOutProc endpCheckModifyState proc hWnd:DWORD invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0 .if eax!=0 invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL .if eax==IDYES invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0 .elseif eax==IDCANCEL mov eax,FALSE ret .endif .endif mov eax,TRUE retCheckModifyState endpSetColor proc LOCAL cfm:CHARFORMAT invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor invoke RtlZeroMemory,addr cfm,sizeof cfm mov cfm.cbSize,sizeof cfm mov cfm.dwMask,CFM_COLOR push TextColor pop cfm.crTextColor invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm retSetColor endpOptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD LOCAL clr:CHOOSECOLOR .if uMsg==WM_INITDIALOG .elseif uMsg==WM_COMMAND mov eax,wParam shr eax,16 .if ax==BN_CLICKED mov eax,wParam .if ax==IDCANCEL invoke SendMessage,hWnd,WM_CLOSE,0,0 .elseif ax==IDC_BACKCOLORBOX invoke RtlZeroMemory,addr clr,sizeof clr mov clr.lStructSize,sizeof clr push hWnd pop clr.hwndOwner push hInstance pop clr.hInstance push BackgroundColor pop clr.rgbResult mov clr.lpCustColors,offset CustomColors mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT invoke ChooseColor,addr clr .if eax!=0 push clr.rgbResult pop BackgroundColor invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX invoke InvalidateRect,eax,0,TRUE .endif .elseif ax==IDC_TEXTCOLORBOX invoke RtlZeroMemory,addr clr,sizeof clr mov clr.lStructSize,sizeof clr push hWnd pop clr.hwndOwner push hInstance pop clr.hInstance push TextColor pop clr.rgbResult mov clr.lpCustColors,offset CustomColors mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT invoke ChooseColor,addr clr .if eax!=0 push clr.rgbResult pop TextColor invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX invoke InvalidateRect,eax,0,TRUE .endif .elseif ax==IDOK invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0 push eax invoke SetColor pop eax invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0 invoke EndDialog,hWnd,0 .endif .endif .elseif uMsg==WM_CTLCOLORSTATIC invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX .if eax==lParam invoke CreateSolidBrush,BackgroundColor ret .else invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX .if eax==lParam invoke CreateSolidBrush,TextColor ret .endif .endif mov eax,FALSE ret .elseif uMsg==WM_CLOSE invoke EndDialog,hWnd,0 .else mov eax,FALSE ret .endif mov eax,TRUE retOptionProc endpSearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD .if uMsg==WM_INITDIALOG push hWnd pop hSearch invoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWN invoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer .elseif uMsg==WM_COMMAND mov eax,wParam shr eax,16 .if ax==BN_CLICKED mov eax,wParam .if ax==IDOK mov uFlags,0 invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer .if eax!=0 invoke IsDlgButtonChecked,hWnd,IDC_DOWN .if eax==BST_CHECKED or uFlags,FR_DOWN mov eax,findtext.chrg.cpMin
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -