The problem is that there are three
versions of richedit control up till now. Version 1,2, and 3. The table
below shows you the name of the DLL for each version.
DLL Name | RichEdit version | Richedit Class Name |
---|---|---|
Riched32.dll | 1.0 | RICHEDIT |
RichEd20.dll | 2.0 | RICHEDIT20A |
RichEd20.dll | 3.0 | RICHEDIT20A |
You can notice that richedit version 2 and 3 use the same DLL name. They also use the same class name! This can pose a problem if you want to use specific features of richedit 3.0. Up to now, I haven't found an official method of differentiating between version 2.0 and 3.0. However, there is a workaround which works ok, I'll show you later.
; data [RichEditDLL: 'RichEd20.dll',0] ..... ; data? [hRichEditDLL: D§ ?] ; code call 'KERNEL32.LoadLibraryA' RichEditDLL mov D§hRichEditDLL eax ...... call 'KERNEL32.FreeLibrary' D§hRichEditDLLWhen the richedit dll is loaded, it registers the RichEdit window class. Thus it's imperative that you load the DLL before you create the control. The names of the richedit control classes are also different. Now you may have a question: how do I know which version of richedit control should I use? Using the latest version is not always appropriate if you don't require the extra features. So below is the table that shows the features provided by each version of richedit control.
Feature | Version 1.0 | Version 2.0 | Version 3.0 |
---|---|---|---|
selection bar |
|
|
|
unicode editing |
|
|
|
character/paragraph formatting |
|
|
|
search for text |
|
|
|
OLE embedding |
|
|
|
Drag and drop editing |
|
|
|
Undo/Redo |
|
|
|
automatic URL recognition |
|
|
|
Accelerator key support |
|
|
|
Windowless operation |
|
|
|
Line break |
|
|
|
Zoom |
|
||
Paragraph numbering |
|
||
simple table |
|
||
normal and heading styles |
|
||
underline coloring |
|
||
hidden text |
|
||
font binding |
|
The above table is by no means comprehensive: I only list the important features.
; const [RichEditID 300] ; data [RichEditDLL: B§ 'RichEd20.dll',0 RichEditClass: 'RichEdit20A',0] ... .data? [hRichEditDLL: D§ ? hwndRichEdit: D§ ?] .code ..... call 'KERNEL32.LoadLibraryA' RichEditDLL mov D§hRichEditDLL eax call 'USER32.CreateWindowExA' 0 RichEditClass,
&WS_VISIBLE__&ES_MULTILINE__&WS_CHILD__&WS_VSCROLL__&WS_HSCROLL, &CW_USEDEFAULT,&CW_USEDEFAULT,&CW_USEDEFAULT,&CW_USEDEFAULT,
D§hWnd,RichEditID,D§hInstance,0 mov D§hwndRichEdit,eax
wParam
== color option. The value of 0 in this parameter specifies that Windows
uses the color value in lParam as the
background color. If this value is nonzero, Windows uses the Windows system
background color. Since we send this message to change the background color,
we must pass 0 in wParam.
lParam
== specifies the COLORREF structure
of the color you want to set if wParam is 0.
For example, if I want to set the background color to pure blue, I would issue this following line:
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETBKGNDCOLOR 0 0FF0000To set the text color, richedit control provides another new message, EM_SETCHARFORMAT, for the job. This message controls the text formatting of a range to characters in selection or all text in the control. This message has the following syntax:
wParam
== formatting options:
SCF_ALL | The operation affects all text in the control. |
SCF_SELECTION | The operation affects only the text in selection |
SCF_WORD or SCF_SELECTION | Affects the word in selection. If the selection is empy, ie, only the caret is in the word, the operation affects that word. SCF_WORD flag must be used with SCF_SELECTION. |
lParam == pointer to a CHARFORMAT or CHARFORMAT2 structure that specifies the text formatting to be applied. CHARFORMAT2 is available for richedit 2.0 and above only. This doesn't mean that you must use CHARFORMAT2 with RichEdit 2.0 or above. You can still use CHARFORMAT if the added features in CHARFORMAT2 are not necessary for your need.
[CHARFORMATA:
cbSize: D§ ?
dwMask: D§ ?
dwEffects: D§ ?
yHeight: D§ ?
yOffset: D§ ?
crTextColor: D§ ?
bCharSet: D§ ?
bPitchAndFamily: D§ ?]
[szFaceName: B§ ? # (BYTE LF_FACESIZE) ]
[wPad2: W§ ? ]
Field Name | Description | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
cbSize | The size of the structure. RichEdit control uses this field to determine the version of the structure whether it is CHARFORMAT or CHARFORMAT2 | ||||||||||||||||||||
dwMask | Bit flags that determine which
of the following members are valid.
|
||||||||||||||||||||
dwEffects | The character effects. Can
be the combination of the following values
|
||||||||||||||||||||
yHeight | Character height, in twips (1/1440 of an inch or 1/20 of a printer's point). | ||||||||||||||||||||
yOffset | Character offset, in twips, from the baseline. If the value of this member is positive, the character is a superscript; if it is negative, the character is a subscript. | ||||||||||||||||||||
crTextColor | Text color. This member is ignored if the CFE_AUTOCOLOR character effect is specified. | ||||||||||||||||||||
bCharSet | Character set value | ||||||||||||||||||||
bPitchAndFamily | Font family and pitch. | ||||||||||||||||||||
szFaceName | Null-terminated character array specifying the font name | ||||||||||||||||||||
_wPad2 | Padding |
From examination of the structure, you'll see that we can change the text effects (bold,italic, strikeout,underline), text color (crTextColor) and font face/size/character set. A notable flag is CFE_RPOTECTED. The text with this flag is marked as protected which means that when the user tries to modify it, EN_PROTECTED notification message will be sent to the parent window. And you can allow the change to happen or not.
CHARFORMAT2 adds more text styles such as font weight, spacing,text background color, kerning, etc. If you don't need these extra features, simply use CHARFORMAT.
To set text formatting, you have to think about the range of text you want to apply to. Richedit control introduces the notion of character text range. Richedit control gives each character a number starting from 0: the first characterin the control has Id of 0, the second character 1 and so on. To specify a text range, you must give the richedit control two numbers: the IDs of the first and the last character of the range. To apply the text formatting with EM_SETCHARFORMAT, you have at most three choices:
To put it simply, you provide the address of a callback function to the richedit control. And richedit control will call that callback, passing the address of the buffer to it, when it's ready. The callback will fill the buffer with the data it wants to send to the control or read the data from the buffer and then waits for the next call until the operation is finished. This paradigm is used for both streaming in (setting the text) and streaming out (getting the text out of the control). You'll see that this method is more efficient: the buffer is provided by the richedit control itself so the data are divided into chunks. The operations involve two messages: EM_STREAMIN and EM_STREAMOUT
Both EM_STREAMIN and EM_STREAMOUT use the same syntax:
wParam
== formatting options.
SF_RTF | The data is in the rich-text format (RTF) |
SF_TEXT | The data is in the plain text format |
SFF_PLAINRTF | Only the keywords common to all languages are streamed in. |
SFF_SELECTION | If specified, the target of the operation is the text currently in selection. If you stream the text in, the text replaces the current selection. If you stream the text out, only the text currently in selection is streamed out. If this flag is not specified, the operation affects the whole text in the control. |
SF_UNICODE | (Available on RichEdit 2.0 or later) Specify the unicode text. |
lParam == point to an EDITSTREAM structure which has the following definition:
[EDITSTREAM: dwCookie: D§ ? dwError: D§ ? pfnCallback: D§ ?]
dwCookie | application-defined value that will be passed to the callback function speficied in pfnCallback member below. We normally pass some important value to the callback function such as the file handle to use in the stream-in/out procedure. |
dwError | Indicates the results of the stream-in (read) or stream-out (write) operation. A value of zero indicates no error. A nonzero value can be the return value of the EditStreamCallback function or a code indicating that the control encountered an error. |
pfnCallback | Pointer to an EditStreamCallback function, which is an application-defined function that the control calls to transfer data. The control calls the callback function repeatedly, transferring a portion of the data with each call |
The editstream callback function has the following definition:
EditStreamCallback proto dwCookie:DWORD, pBuffer:DWORD, NumBytes:DWORD, pBytesTransferred:DWORDYou have to create a function with the above prototype in your program. And then pass its address to EM_STREAMIN or EM_STREAMOUT via EDITSTREAM structure.
For stream-in operation (settting the text in the richedit control):
dwCookie: the application-defined value you pass to EM_STREAMIN via EDITSTREAM structure. We almost always pass the file handle of the file we want to set its content to the control here. pBuffer: points to the buffer provided by the richedit control that will receive the text from your callback function. NumBytes: the maximum number of bytes you can write the the buffer (pBuffer) in this call. You MUST always obey this limit, ie, you can send less data than the value in NumBytes but must not send more data than this value. You can think of this value as the size of the buffer in pBuffer. pBytesTransferred: points to a dword that you must set the value indicating the number of bytes you actually transferred to the buffer. This value is usually identical to the value in NumBytes. The exception is when the data is to send is less than the size of the buffer provided such as when the end of file is reached.For stream-out operation (getting the text out of the richedit control):
dwCookie: Same as the stream-in operation. We usually pass the file handle we want to write the data to in this parameter. pBuffer: points to the buffer provided by the richedit control that is filled with the data from the richedit control. To obtain its size, you must examine the value of NumBytes. NumBytes: the size of the data in the buffer pointed to by pBuffer. pBytesTransferred: points to a dword that you must set the value indicating the number of bytes you actually read from the buffer.The callback function returns 0 to indicate success and richedit control will continue calling the callback function if there is still data left to read/write. If some error occurs during the process and you want to stop the operation, returns a non-zero value and the richedit control will discard the data pointed to by pBuffer. The error/success value will be filled in the dwError field of EDITSTREAM so you can examine the error/success status of the stream operation after SendMessage returns.
____________________________________________________________________________________________ [IDD_OPTIONDLG 1000 IDC_BACKCOLORBOX 1001 IDC_TEXTCOLORBOX 1002] [RichEditID 300] [ClassName: B§ 'IczEditClass',0 AppName : B§ 'IczEdit version 1.0',0 RichEditDLL: B§ 'riched20.dll',0 RichEditClass: B§ 'RichEdit20A',0 NoRichEdit: B§ 'Cannot find riched20.dll',0 ASMFilterString: B§ 'ASM Source code (*.asm)',0,'*.asm',0 B§ 'All Files (*.*)',0,'*.*',0,0 OpenFileFail: B§ 'Cannot open the file',0 WannaSave: B§ 'The data in the control is modified. Want to save it?',0] [FileOpened: D§ &FALSE BackgroundColor: D§ 0FFFFFF ; default to white TextColor: D§ 0] ; default to black [hInstance: D§ ? hRichEdit: D§ ? hwndRichEdit: D§ ?] [FileName: B§ ? #260] [AlternateFileName: B§ ? #260] [CustomColors: D§ ? #16] ____________________________________________________________________________________________ ____________________________________________________________________________________________ [WNDCLASSEX: @cbSize: D§ len @style: D§ &CS_HREDRAW__&CS_VREDRAW @lpfnWndProc: D§ MainWindowProc @cbClsExtra: D§ 0 @cbWndExtra: D§ 0 @hInstance: D§ 0 @hIcon: D§ 0 @hCursor: D§ 0 @hbrBackground: D§ &COLOR_WINDOW+1 @lpszMenuName: D§ M00_Menu @lpszClassName: D§ ClassName @hIconSm: D§ 0] [MSG: @hwnd: D§ 0 @message: D§ 0 @wParam: D§ 0 @lParam: D§ 0 @time: D§ 0 @MSG@pt: @MSG@pt@x: D§ 0 @MSG@pt@y: D§ 0] [hwnd: D§ 0] [M00_Menu 1000 M00_Open 1001 M00_Close 1002 M00_Save 1003 M00_save_As 1004 M00_Exit 1005 M00_Undo 1006 M00_Redo 1007 M00_Copy 1008 M00_Cut 1009 M00_Paste 1010 M00_Delete 1011 M00_select_All 1012 M00_Options 1013] ____________________________________________________________________________________________ ____________________________________________________________________________________________ [&SCF_ALL 4] ____________________________________________________________________________________________ ____________________________________________________________________________________________ Main: call 'KERNEL32.GetModuleHandleA' &NULL mov D§hInstance eax, D§WNDCLASSEX@hInstance eax call 'KERNEL32.LoadLibraryA' RichEditDLL .If eax <> 0 mov D§hRichEdit eax .Else call 'USER32.MessageBoxA' 0 NoRichEdit AppName &MB_OK__&MB_ICONERROR | jmp L9>> .End_If call 'USER32.LoadIconA' &NULL &IDI_APPLICATION mov D§WNDCLASSEX@hIcon eax, D§WNDCLASSEX@hIconSm eax call 'USER32.LoadCursorA' &NULL &IDC_ARROW mov D§WNDCLASSEX@hCursor eax call 'USER32.RegisterClassExA' WNDCLASSEX call 'USER32.CreateWindowExA' &NULL ClassName AppName, &WS_OVERLAPPEDWINDOW &CW_USEDEFAULT, &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &NULL &NULL, D§hInstance &NULL mov D§hwnd eax call 'USER32.ShowWindow' D§hwnd &SW_SHOWNORMAL call 'USER32.UpdateWindow' D§hwnd L1: call 'USER32.GetMessageA' MSG 0 0 0 | On eax = 0, jmp L8> call 'USER32.TranslateMessage' MSG call 'USER32.DispatchMessageA' MSG | jmp L1< L8: call 'KERNEL32.FreeLibrary' D§hRichEdit L9: call 'KERNEL32.ExitProcess' 0 ____________________________________________________________________________________________ ____________________________________________________________________________________________ Proc StreamInProc: arguments @hFile @pBuffer @NumBytes @pBytesRead call 'KERNEL32.ReadFile' D@hFile D@pBuffer D@NumBytes D@pBytesRead 0 xor eax 1 EndP Proc StreamOutProc: Arguments @hFile @pBuffer @NumBytes @pBytesWritten call 'KERNEL32.WriteFile' D@hFile D@pBuffer D@NumBytes D@pBytesWritten 0 xor eax 1 EndP Proc CheckModifyState: Argument @hWnd call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETMODIFY 0 0 .If eax <> 0 call 'USER32.MessageBoxA' D§hWnd WannaSave AppName &MB_YESNOCANCEL If eax = &IDYES call 'USER32.SendMessageA' D@hWnd &WM_COMMAND M00_SAVE 0 Else _If eax = &IDCANCEL mov eax &FALSE | Exit End_If .Endif mov eax &TRUE EndP [CHARFORMAT: @cbSize: D§ 60 @dwMask: D§ &CFM_COLOR @dwEffects: D§ 0 ; &CFE_AUTOCOLOR ; ? @yHeight: D§ 0 @yOffset: D§ 0 @crTextColor: D§ 0 @bCharSet: B§ 0 @bPitchAndFamily: B§ 0] [@szFaceName: B§ 0 #32] ; &LF_FACESIZE [wPad2: W§ 0] SetColor: call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETBKGNDCOLOR 0 D§BackgroundColor move D§CHARFORMAT@crTextColor D§TextColor call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETCHARFORMAT &SCF_ALL CHARFORMAT ret [CHOOSECOLOR: @lStructSize: D§ len @hwndOwner: D§ 0 @hInstance: D§ 0 @rgbResult: D§ 0 @lpCustColors: D§ CustomColors @Flags: D§ &CC_RGBINIT @lCustData: D§ 0 @lpfnHook: D§ 0 @lpTemplateName: D§ 0] Proc OptionProc: Arguments @hWnd @uMsg @wParam @lParam pushad ...If D@uMsg = &WM_INITDIALOG ; returns &TRUE ...Else_If D@uMsg = &WM_COMMAND mov eax D@wParam | shr eax 16 ..If ax = &BN_CLICKED mov eax D@wParam .If ax = &IDCANCEL call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0 .Else_If ax = IDC_BACKCOLORBOX move D§CHOOSECOLOR@hwndOwner D@hwnd move D§ CHOOSECOLOR@hInstance D§hInstance Move D§CHOOSECOLOR@rgbResult D§BackgroundColor call 'COMDLG32.ChooseColorA' CHOOSECOLOR If eax <> 0 move D§BackgroundColor D§CHOOSECOLOR@rgbResult call 'USER32.GetDlgItem' D@hWnd IDC_BACKCOLORBOX call 'USER32.InvalidateRect' eax 0 &TRUE End_If .Else_If ax = IDC_TEXTCOLORBOX move D§CHOOSECOLOR@hwndOwner D@hwnd move D§ CHOOSECOLOR@hInstance D§hInstance move D§CHOOSECOLOR@rgbResult D§TextColor call 'COMDLG32.ChooseColorA' CHOOSECOLOR If eax <> 0 move D§TextColor D§CHOOSECOLOR@rgbResult call 'USER32.GetDlgItem' D@hWnd IDC_TEXTCOLORBOX call 'USER32.InvalidateRect' eax 0 &TRUE End_If .Else_If ax = &IDOK ;================================================================================ ; Save the modify state of the richedit control because changing the text color changes the ; modify state of the richedit control. ;================================================================================== call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETMODIFY 0 0 push eax call SetColor pop eax call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY eax 0 call 'USER32.EndDialog' D@hWnd 0 .End_If ..End_If ...Else_If D@uMsg = &WM_CTLCOLORSTATIC call 'USER32.GetDlgItem' D@hWnd IDC_BACKCOLORBOX .If eax = D@lParam popad call 'GDI32.CreateSolidBrush' D§BackgroundColor Exit .Else call 'USER32.GetDlgItem' D@hWnd IDC_TEXTCOLORBOX If eax = D@lParam popad call 'GDI32.CreateSolidBrush' D§TextColor Exit End_If .End_If popad | mov eax &FALSE | Exit ...Else_If D@uMsg = &WM_CLOSE call 'USER32.EndDialog' D@hWnd 0 ...Else popad | mov eax &FALSE | Exit ...End_If popad | mov eax &TRUE EndP ____________________________________________________________________________________________ ____________________________________________________________________________________________ [CHARRANGE: @cpMin: D§ 0 @cpMax: D§ 0] [OPENFILENAME: @lStructSize: D§ Len @hWndOwner: D§ 0 @hInstance: D§ 0 @lpstrFilter: D§ ASMFilterString @lpstrCustomFilter: D§ 0 @nMaxCustFilter: D§ 0 @nFilterIndex: D§ 0 @lpstrFile: D§ FileName @nMaxFile: D§ 260 @lpstrFileTitle: D§ 0 @nMaxFileTitle: D§ 260 @lpstrInitialDir: D§ 0 @lpstrTitle: D§ 0 @Flags: D§ 0 @nFileOffset: W§ 0 @nFileExtension: W§ 0 @lpstrDefExt: D§ 0 @lCustData: D§ 0 @lpfnHook: D§ 0 @lpTemplateName: D§ 0] [Buffer: B§ ? #256] [EDITSTREAM: @dwCookie: D§ 0 @dwError: D§ 0 @pfnCallback: D§ 0] [hFile: 0] Proc MainWindowProc: Arguments @hWnd @uMsg @wParam @lParam pushad ...If D@uMsg = &WM_CREATE call 'USER32.CreateWindowExA' &WS_EX_CLIENTEDGE RichEditClass 0, &WS_CHILD__&WS_VISIBLE__&ES_MULTILINE__&WS_VSCROLL__&WS_HSCROLL__&ES_NOHIDESEL, &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT D@hWnd, RichEditID D§hInstance 0 mov D§hwndRichEdit eax ;============================================================= ; Set the text limit. The default is 64K ;============================================================= call 'USER32.SendMessageA' D§hwndRichEdit &EM_LIMITTEXT 0-1 0 ;============================================================= ; Set the default text/background color ;============================================================= call SetColor call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0 call 'USER32.SendMessageA' D§hwndRichEdit &EM_EMPTYUNDOBUFFER 0 0 ...Else_If D@uMsg = &WM_INITMENUPOPUP mov eax D@lParam ..If ax = 0 ; file menu .If D§FileOpened = &TRUE ; a file is already opened call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_ENABLED .Else call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_GRAYED .End_If ..Else_If ax = 1 ; edit menu ;============================================================================= ; Check whether there is some text in the clipboard. If so, we enable the paste menuitem ;============================================================================= call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANPASTE &CF_TEXT,0 If eax = 0 ; no text in the clipboard call 'USER32.EnableMenuItem' D@wParam M00_PASTE &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_PASTE &MF_ENABLED End_If _______________________________________ ; check whether the undo queue is empty _______________________________________ call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANUNDO 0 0 If eax = 0 call 'USER32.EnableMenuItem' D@wParam M00_UNDO &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_UNDO &MF_ENABLED End_If _______________________________________ ; check whether the redo queue is empty _______________________________________ call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANREDO 0 0 If eax = 0 call 'USER32.EnableMenuItem' D@wParam M00_REDO &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_REDO &MF_ENABLED End_If _____________________________________________________________________ ; check whether there is a current selection in the richedit control. ; If there is, we enable the cut/copy/delete menuitem _____________________________________________________________________ call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 CHARRANGE mov eax D§CHARRANGE@cpMin If eax = D§CHARRANGE@cpMax ; no current selection call 'USER32.EnableMenuItem' D@wParam M00_COPY &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_CUT &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_DELETE &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_COPY &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_CUT &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_DELETE &MF_ENABLED End_If ..End_If ...Else_If D@uMsg = &WM_COMMAND ..If D@lParam = 0 ; menu commands mov eax D@wParam .If ax = M00_OPEN call OpenCommand D@hWnd .Else_If ax = M00_CLOSE call CheckModifyState D@hWnd If eax = &TRUE call 'USER32.SetWindowTextA' D§hwndRichEdit 0 mov D§FileOpened &FALSE End_If .Else_If ax = M00_SAVE call 'KERNEL32.CreateFileA' FileName &GENERIC_WRITE &FILE_SHARE_READ, &NULL &CREATE_ALWAYS &FILE_ATTRIBUTE_NORMAL 0 If eax <> &INVALID_HANDLE_VALUE call InvalidFile Else call 'USER32.MessageBoxA' D@hWnd OpenFileFail AppName, &MB_OK__&MB_ICONERROR End_If .Else_If ax = M00_COPY call 'USER32.SendMessageA' D§hwndRichEdit &WM_COPY 0 0 .Else_If ax = M00_CUT call 'USER32.SendMessageA' D§hwndRichEdit &WM_CUT 0 0 .Else_If ax = M00_PASTE call 'USER32.SendMessageA' D§hwndRichEdit &WM_PASTE 0 0 .Else_If ax = M00_DELETE call 'USER32.SendMessageA' D§hwndRichEdit &EM_REPLACESEL &TRUE 0 .Else_If ax = M00_SELECTALL mov D§CHARRANGE@cpMin 0 mov D§CHARRANGE@cpMax 0-1 call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0 CHARRANGE .Else_If ax = M00_UNDO call 'USER32.SendMessageA' D§hwndRichEdit &EM_UNDO 0 0 .Else_If ax = M00_REDO call 'USER32.SendMessageA' D§hwndRichEdit &EM_REDO 0 0 .Else_If ax = M00_OPTIONS call 'USER32.DialogBoxParamA' D§hInstance IDD_OPTIONDLG D@hWnd OptionProc 0 .Else_If ax = M00_SAVEAS move D§OPENFILENAME@hwndOwner D@hwnd move D§OPENFILENAME@hInstance D§hInstance mov D§OPENFILENAME@lpstrFilter ASMFilterString mov D§OPENFILENAME@lpstrFile AlternateFileName mov D§AlternateFileName 0 mov D§OPENFILENAME@Flags &OFN_FILEMUSTEXIST__&OFN_HIDEREADONLY__&OFN_PATHMUSTEXIST call 'COMDLG32.GetSaveFileNameA' OPENFILENAME If eax <> 0 call 'KERNEL32.CreateFileA' AlternateFileName &GENERIC_WRITE, &FILE_SHARE_READ &NULL &CREATE_ALWAYS, &FILE_ATTRIBUTE_NORMAL 0 On eax <> &INVALID_HANDLE_VALUE, call InvalidFile End_If .Else_If ax = M00_EXIT call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0 .End_If ..End_If ...Else_If D@uMsg = &WM_CLOSE call CheckModifyState D@hWnd If eax = &TRUE call 'USER32.DestroyWindow' D@hWnd End_If ...Else_If D@uMsg = &WM_SIZE mov eax D@lParam, edx eax and eax 0FFFF | shr edx 16 call 'USER32.MoveWindow' D§hwndRichEdit 0 0 eax edx &TRUE ...Else_If D@uMsg = &WM_DESTROY call 'USER32.PostQuitMessage' &NULL ...Else popad call 'USER32.DefWindowProcA' D@hWnd D@uMsg D@wParam D@lParam Exit ...End_If popad | mov eax &FALSE EndP InvalidFile: mov D§hFile eax _____________________________ ; stream the text to the file _____________________________ mov D§EDITSTREAM@dwCookie eax mov D§EDITSTREAM@pfnCallback StreamOutProc call 'USER32.SendMessageA' D§hwndRichEdit &EM_STREAMOUT &SF_TEXT EDITSTREAM ______________________________________ ; Initialize the modify state to false ______________________________________ call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0 call 'KERNEL32.CloseHandle' D§hFile ret Proc OpenCommand: Argument @hWnd move D§OPENFILENAME@hwndOwner D@hWnd move D§OPENFILENAME@hInstance D§hInstance mov D§FileName 0 mov D§OPENFILENAME@Flags &OFN_FILEMUSTEXIST__&OFN_HIDEREADONLY__&OFN_PATHMUSTEXIST call 'COMDLG32.GetOpenFileNameA' OPENFILENAME ...If eax <> 0 call 'KERNEL32.CreateFileA' FileName &GENERIC_READ, &FILE_SHARE_READ &NULL, &OPEN_EXISTING &FILE_ATTRIBUTE_NORMAL 0 .If eax <> &INVALID_HANDLE_VALUE mov D§hFile eax ___________________________________________ ; stream the text into the richedit control ___________________________________________ mov D§EDITSTREAM@dwCookie eax mov D§EDITSTREAM@pfnCallback StreamInProc call 'USER32.SendMessageA' D§hwndRichEdit &EM_STREAMIN, &SF_TEXT EDITSTREAM ______________________________________ ; Initialize the modify state to false ______________________________________ call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0 call 'KERNEL32.CloseHandle' D§hFile mov B§FileOpened &TRUE .Else call 'USER32.MessageBoxA' D@hWnd OpenFileFail AppName, &MB_OK__&MB_ICONERROR .End_If ...End_If EndP
call 'KERNEL32.LoadLibraryA' RichEditDLL .If eax <> 0 mov D§hRichEdit eax .Else call 'USER32.MessageBoxA' 0 NoRichEdit AppName &MB_OK__&MB_ICONERROR | jmp L9>> .End_IfAfter the dll is loaded successfully, we proceed to create a normal window which will be the parent of the richedit control. Within the WM_CREATE handler, we create the richedit control:
...If D@uMsg = &WM_CREATE call 'USER32.CreateWindowExA' &WS_EX_CLIENTEDGE RichEditClass 0, &WS_CHILD__&WS_VISIBLE__&ES_MULTILINE__&WS_VSCROLL__&WS_HSCROLL__&ES_NOHIDESEL, &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT D@hWnd, RichEditID D§hInstance 0 mov D§hwndRichEdit eaxNote that we specify ES_MULTILINE style else the control will be a single-lined one.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_LIMITTEXT 0-1 0After the richedit control is created, we must set the new text limit on it. By default, the richedit control has 64K text limit, the same as a simple multi-line edit control. We must extend this limit to allow it to operate with larger files. In the above line, I specify -1 which amounts to 0FFFFFFFFh, a very large value.
call SetColorNext, we set the text/background color. Since this operation can be performed in other part of the program, I put the code in a function named SetColor.
SetColor: call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETBKGNDCOLOR 0 D§BackgroundColorSetting the background color of the richedit control is a straightforward operation: just send EM_SETBKGNDCOLOR message to the richedit control. (If you use a multi-line edit control, you have to process WM_CTLCOLOREDIT). The default background color is white.
move D§CHARFORMAT@crTextColor D§TextColorAfter the background color is set, we fill in the members of CHARFORMAT in order to set the text color. Note that we fill cbSize with the size of the structure so the richedit control knows we are sending it CHARFORMAT, not CHARFORMAT2. dwMask has only one flag, CFM_COLOR, because we only want to set the text color and crTextColor is filled with the value of the desired text color.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETCHARFORMAT &SCF_ALL CHARFORMAT retAfter settting the color, you have to empty undo buffer simply because the act of changing text/background color is undo-able. We send EM_EMPTYUNDOBUFFER message to achieve this.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EMPTYUNDOBUFFER,0,0After filling the CHARFORMAT structure, we send EM_SETCHARFORMAT to the richedit control, specifying SCF_ALL flag in wParam to indicate that we want the text formatting to be applied to all text in the control.
Note that when we first created the richedit control, we didn't specify its size/position at that time. That's because we want it to cover the whole client area of the parent window. We resize it whenever the size of the parent window changes.
...Else_If D@uMsg = &WM_SIZE mov eax D@lParam, edx eax and eax 0FFFF | shr edx 16 call 'USER32.MoveWindow' D§hwndRichEdit 0 0 eax edx &TRUEIn the above code snippet, we use the new dimension of the client area passed in lParam to resize the richedit control with MoveWindow.
When the user clicks on the File/Edit menu bar, we process WM_INITPOPUPMENU so that we can prepare the states of the menuitems in the submenu before displaying it to the user. For example, if a file is already opened in the richedit control, we want to disable the open menuitem and enable all the remaining menuitems.
In the case of the File menu bar, we use the variable FileOpened as the flag to determine whether a file is already opened. If the value in this variable is TRUE, we know that a file is already opened.
...Else_If D@uMsg = &WM_INITMENUPOPUP mov eax D@lParam ..If ax = 0 ; file menu .If D§FileOpened = &TRUE ; a file is already opened call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_ENABLED .Else call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_GRAYED .End_IfAs you can see, if a file is already opened, we gray out the open menuitem and enable the remaining menuitems. The reverse is true of FileOpened is false.
In the case of the edit menu bar, we need to check the state of the richedit control/clipboard first.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANPASTE &CF_TEXT,0 If eax = 0 ; no text in the clipboard call 'USER32.EnableMenuItem' D@wParam M00_PASTE &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_PASTE &MF_ENABLED End_IfWe first check whether some text is available in the clipboard by sending EM_CANPASTE message. If some text is available, SendMessage returns TRUE and we enable the paste menuitem. If not, we gray out the menuitem.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANUNDO 0 0 If eax = 0 call 'USER32.EnableMenuItem' D@wParam M00_UNDO &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_UNDO &MF_ENABLED End_IfNext, we check whether the undo buffer is empty by sending EM_CANUNDO message. If it's not empty, SendMessage returns TRUE and we enable the undo menuitem.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANREDO 0 0 If eax = 0 call 'USER32.EnableMenuItem' D@wParam M00_REDO &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_REDO &MF_ENABLED End_IfWe check the redo buffer by sending EM_CANREDO message to the richedit control. If it's not empty, SendMessage returns TRUE and we enable the redo menuitem.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 CHARRANGE mov eax D§CHARRANGE@cpMin If eax = D§CHARRANGE@cpMax ; no current selection call 'USER32.EnableMenuItem' D@wParam M00_COPY &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_CUT &MF_GRAYED call 'USER32.EnableMenuItem' D@wParam M00_DELETE &MF_GRAYED Else call 'USER32.EnableMenuItem' D@wParam M00_COPY &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_CUT &MF_ENABLED call 'USER32.EnableMenuItem' D@wParam M00_DELETE &MF_ENABLED End_If ..End_IfLastly, we check whether a current selection exists by sending EM_EXGETSEL message. This message uses a CHARRANGE structure which is defined as follows:
[CHARRANGE: cpMin: D§ ? cpMax: D§ ?]cpMin contains the character position index immediately preceding the first character in the range.
After EM_EXGETSEL returns, the CHARRANGE structure is filled with the starting-ending character position indices of the selection range. If there is no current selection, cpMin and cpMax are identical and we gray out the cut/copy/delete menuitems.
When the user clicks the Open menuitem, we display an open file dialog box and if the user selects a file, we open the file and stream its content to the richedit control.
...If eax <> 0 call 'KERNEL32.CreateFileA' FileName &GENERIC_READ, &FILE_SHARE_READ &NULL, &OPEN_EXISTING &FILE_ATTRIBUTE_NORMAL 0 .If eax <> &INVALID_HANDLE_VALUE mov D§hFile eax ___________________________________________ ; stream the text into the richedit control ___________________________________________ mov D§EDITSTREAM@dwCookie eax mov D§EDITSTREAM@pfnCallback StreamInProc call 'USER32.SendMessageA' D§hwndRichEdit &EM_STREAMIN, &SF_TEXT EDITSTREAMAfter the file is successfully opened with CreateFile, we fill the EDITSTREAM structure in preparation for EM_STREAMIN message. We choose to send the handle to the opened file via dwCookie member and pass the address of the stream callback function in pfnCallback.
The stream callback procedure itself is the essence of simplicity.
Proc StreamInProc: arguments @hFile @pBuffer @NumBytes @pBytesRead call 'KERNEL32.ReadFile' D@hFile D@pBuffer D@NumBytes D@pBytesRead 0 xor eax 1 EndPYou can see that all parameters of the stream callback procedure fit perfectly with ReadFile. And the return value of ReadFile is xor-ed with 1 so that if it returns 1 (success), the actual value returned in eax is 0 and vice versa.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0 call 'KERNEL32.CloseHandle' D§hFile mov B§FileOpened &TRUEAfter EM_STREAMIN returns, it means the stream operation is completed. In reality, we must check the value of dwError member of the EDITSTREAM structure.
Richedit (and edit) control supports a flag to indicate whether its content is modified. We can obtain the value of this flag by sending EM_GETMODIFY message to the control. SendMessage returns TRUE if the content of the control was modified. Since we stream the text into the control, it's a kind of a modification. We must set the modify flag to FALSE by sending EM_SETMODIFY with wParam==FALSE to the control to start anew after the stream-in opertion is finished. We immediately close the file and set FileOpened to TRUE to indicate that a file was opened.
When the user clicks on save/saveas menuitem, we use EM_STREAMOUT message to output the content of the richedit control to a file. As with the streamin callback function, the stream-out callback function is simplicity in itself. It fits perfectly with WriteFile.
The text operations such as cut/copy/paste/redo/undo are easily implemented by sending single message to the richedit control, WM_CUT/WM_COPY/WM_PASTE/WM_REDO/WM_UNDO respectively.
The delete/select all operations are done as follows:
.Else_If ax = M00_DELETE call 'USER32.SendMessageA' D§hwndRichEdit &EM_REPLACESEL &TRUE 0 .Else_If ax = M00_SELECTALL mov D§CHARRANGE@cpMin 0 mov D§CHARRANGE@cpMax 0-1 call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0 CHARRANGEThe delete operation affects the currently selection. I send EM_REPLACESEL message with NULL string so the richedit control will replace the currently selected text with the null string.
The select-all operation is done by sending EM_EXSETSEL message, specifying cpMin==0 and cpMax==-1 which amounts to selecting all the text.
When the user selects Option menu bar, we display a dialog box presenting the current background/text colors.
When the user clicks on one of the color boxes, it displays the choose-color dialog box. The "color box" is in fact a static control with SS_NOTIFY and WS_BORDER flag. A static control with SS_NOTIFY flag will notify its parent window with mouse actions on it, such as BN_CLICKED (STN_CLICKED). That's the trick.
.Else_If ax = IDC_BACKCOLORBOX move D§CHOOSECOLOR@hwndOwner D@hwnd move D§ CHOOSECOLOR@hInstance D§hInstance Move D§CHOOSECOLOR@rgbResult D§BackgroundColor call 'COMDLG32.ChooseColorA' CHOOSECOLOR If eax <> 0 move D§BackgroundColor D§CHOOSECOLOR@rgbResult call 'USER32.GetDlgItem' D@hWnd IDC_BACKCOLORBOX call 'USER32.InvalidateRect' eax 0 &TRUE End_IfWhen the user clicks on one of the color box, we fill the members of the CHOOSECOLOR structure and call ChooseColor to display the choose-color dialog box. If the user selects a color, the colorref value is returned in rgbResult member and we store that value in BackgroundColor variable. After that, we force a repaint on the color box by calling InvalidateRect on the handle to the color box. The color box sends WM_CTLCOLORSTATIC message to its parent window.
call 'USER32.GetDlgItem' D@hWnd IDC_TEXTCOLORBOX If eax = D@lParam popad call 'GDI32.CreateSolidBrush' D§TextColor Exit End_IfWithin the WM_CTLCOLORSTATIC handler, we compare the handle of the static control passed in lParam to that of both the color boxes. If the values match, we create a new brush using the color in the variable and immediately return. The static control will use the newly created brush to paint its background.