教程 34: RichEdit 控件:更多的正文操作

你将会了解到关于的RichEdit更多的正文操作。特别是你将会学习到如何搜索/替换正文,定位到某一指定的行号。

下载 例子程序.

Theory

Searching for Text

RichEdit 控件具有几种正文操作,搜索指定正文就是其中的一种。搜索正文是通过发送 EM_FINDTEXT 或者 EM_FINDTEXTEX 消息来完成的。这两个消息有一点很小的不同点。

EM_FINDTEXT
wParam == 搜索选项。 可以是下表中的任意组合值。这些选项对 EM_FINDTEXTEM_FINDTEXTEX 都是一样的。
FR_DOWN 如果指定了这个标志值,搜索操作从当前选定的 end 位置开始,直到控件中正文的 end 位置结束(向下搜索)。这个标志仅影响 RichEdit 2.0 和以后版本: 这个是 RichEdit 1.0 的缺省行为。RichEdit 2.0 或以后版本的缺省行为是在当前选定正文内的从结尾搜索到开始位置(向前搜索)。
概括来说就是,如果你使用 RichEdit 1.0, 无论你做什么都没法影响搜索的方向:它总使用向后搜索。但是如果你使用 RichEdit 2.0 而且你想使用向后搜索的话,你必须指定这个标志值,否则使用的是向前搜索了。
FR_MATCHCASE 如果指定了这个标志值,搜索操作是大小写敏感的,即区分大小写。
FR_WHOLEWORD 如果设置了这个标志值,搜索操作就搜寻匹配指定搜索串的整个词。
实际上,还有更多的标志值,但是它们都是跟非英语系正文操作相关的。
lParam == FINDTEXT 结构的指针。

			FINDTEXT STRUCT
			  chrg          CHARRANGE  <>
			  lpstrText     DWORD      ?
			FINDTEXT ENDS

chrg 是一个 CHARRANGE 结构,其定义如下:

			CHARRANGE STRUCT
			  cpMin  DWORD      ?
			  cpMax  DWORD      ?
			CHARRANGE ENDS

cpMin 包含字符数组中第一个字符的字符索引。
cpMax 包含紧跟在字符数组中最后一个字符的字符的字符索引。

基本上,要搜索一个正文串,你必须指定要搜索的字符范围。
cpMincpMax 的具体意义根据搜索是向后还是向前是不同的。 
如果是向后搜索,cpMin 指定搜索的开始字符索引,而 cpMax 则是结束字符索引。
如果是向前搜索,则反过来才对,也就是说 cpMin 包含结束字符索引而cpMax 包含开始字符索引。

lpstrText 是要搜索的正文串的指针。

EM_FINDTEXT 返回控件中跟搜索串匹配的的正文串的一个字符的索引。如果没找到匹配的则返回 -1。

EM_FINDTEXTEX
wParam == 搜索选项,跟 EM_FINDTEXT 的一样。
lParam == FINDTEXTEX 结构的指针。

			FINDTEXTEX STRUCT
			  chrg          CHARRANGE  <>
			  lpstrText     DWORD      ?
			  chrgText	CHARRANGE <>
			FINDTEXTEX ENDS

FINDTEXTEX 中开始的两个成员是跟 FINDTEXT 结构中的一样的。
chrgText 是一个 CHARRANGE 结构,如果搜索到匹配串的话,其开始/结束字符索引会被填入这个结构中。


EM_FINDTEXTEX 的返回值跟 EM_FINDTEXT的是一样的。

EM_FINDTEXT  EM_FINDTEXTEX 的不同处是 FINDTEXTEX 结构有一个另外的chrgText成员, 如果搜索到匹配串的话,
其开始/结束字符索引会被填入这个成员中。如果我们想对这个正文串进行更多的正文操作的话,有这个就方便多了。

替换/插入正文

RichEdit 控件提供了 EM_SETTEXTEX 来进行正文替换/插入操作。这个消息混合了 WM_SETTEXTEM_REPLACESEL 的功能. 它具有以下语法:

	EM_SETTEXTEX
	wParam == SETTEXTEX 结构的指针。

			SETTEXTEX STRUCT
			  flags          DWORD      ?
			  codepage       DWORD      ?
			SETTEXTEX ENDS

	flags 可以是以下值的组合:
ST_DEFAULT 删除Undo堆栈,丢弃RTF格式,替换所有的正文。
ST_KEEPUNDO 保留Undo堆栈。
ST_SELECTION 替换选定正文并且保留RTF格式
	codepage 是一个常量,指定你的正文想要的代码页。我们通常简单的使用 CP_ACP

正文选择

我们可以使用消息 EM_SETSEL 或者 EM_EXSETSEL 来编程选择正文.其中任意的一个都可以工作的很好。要使用哪一个消息要根据可用的字符索引格式来选择。如果它们保存在一个 CHARRANGE 结构中,则使用 EM_EXSETSEL更容易实现。

	EM_EXSETSEL
	wParam == 没有使用,必须为 0 。
	lParam == CHARRANGE 结构的指针,包含想要选定的正文字符范围。

事件通知

在使用多行Edit控件时,你必须子类化它以便得到输入信息象鼠标/键盘事件等。RichEdit 控件提供了一个更好的方案,它可以把这些消息通知父窗口。为了注册得到通知消息,父窗口发送 EM_SETEVENTMASK 消息给 RichEdit 控件,指定它对哪些消息感兴趣。 EM_SETEVENTMASK 具有以下的语法:

	EM_SETEVENTMASK
	wParam == 没有使用,必须为 0 。
	lParam == 事件掩码值。他可以是以下表格里标志值的任意组合。
ENM_CHANGE 发送 EN_CHANGE 通知
ENM_CORRECTTEXT 发送 EN_CORRECTTEXT 通知
ENM_DRAGDROPDONE 发送 EN_DRAGDROPDONE 通知
ENM_DROPFILES 发送 EN_DROPFILES 通知
ENM_KEYEVENTS 为键盘消息发送 EN_MSGFILTER 通知
ENM_LINK Rich Edit 2.0 或以后版本: 当鼠标在具有 CFE_LINK 风格的正文上面移过,而且执行了一个或几个鼠标动作时,就发送 EN_LINK 通知。
ENM_MOUSEEVENTS 为鼠标消息发送 EN_MSGFILTER 通知。
ENM_OBJECTPOSITIONS 发送 EN_OBJECTPOSITIONS 通知
ENM_PROTECTED 发送 EN_PROTECTED 通知
ENM_REQUESTRESIZE 发送 EN_REQUESTRESIZE 通知
ENM_SCROLL 发送 EN_HSCROLLEN_VSCROLL 通知
ENM_SCROLLEVENTS 为鼠标滑轮发送 EN_MSGFILTER 通知。
ENM_SELCHANGE 发送 EN_SELCHANGE 通知
ENM_UPDATE

发送 EN_UPDATE 通知
Rich Edit 2.0 和以后版本: 这个标志值会被忽略,而经常发送 EN_UPDATE 通知。然而如果 RichEdit 3.0 模拟 RichEdit 1.0的话,你必须使用这个标志值来发送 EN_UPDATE 通知

上面的所有通知都被做为 WM_NOTIFY 消息来发送:你必须检查 NMHDR 结构的 code 成员来得到通知消息。譬如,如果你想注册得到鼠标消息(也就是说,你想提供一给上下文相关的弹出菜单), 你需要象下面这样做:

	invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
	.....
	.....
	WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
	.....
	....
		.elseif uMsg==WM_NOTIFY
			push esi
			mov esi,lParam
			assume esi:ptr NMHDR
			.if [esi].code==EN_MSGFILTER
				....
				[ do something here]
				....
			.endif
			pop esi

例子:

下面的例子是第33篇指南里的 IczEdit 的改进版。它为程序增加了搜索/替换功能和加速键。同时它处理鼠标消息,点右键时会出现一个弹出菜单。

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001
IDR_MAINACCEL                 equ  105
IDD_FINDDLG                    equ 102
IDD_GOTODLG                    equ 103
IDD_REPLACEDLG                 equ 104
IDC_FINDEDIT                  equ  1000
IDC_MATCHCASE                  equ 1001
IDC_REPLACEEDIT                 equ 1001
IDC_WHOLEWORD                  equ 1002
IDC_DOWN                       equ 1003
IDC_UP                       equ   1004
IDC_LINENO                   equ   1005
IDM_FIND                       equ 40014
IDM_FINDNEXT                  equ  40015
IDM_REPLACE                     equ 40016
IDM_GOTOLINE                   equ 40017
IDM_FINDPREV                  equ  40018
RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 2.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
				db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; default to white
TextColor dd 0		; default to black
hSearch dd ?		; handle to the search/replace dialog box
hAccel dd ?

.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 <>

.code
start:
	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 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
	ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
	invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
	xor eax,1
	ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
	invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
	xor eax,1
	ret
StreamOutProc endp

CheckModifyState 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
	ret
CheckModifyState endp

SetColor 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
	ret
SetColor endp

OptionProc 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
	ret
OptionProc endp

SearchProc 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
						.if eax!=findtext.chrg.cpMax
							push findtext.chrg.cpMax
							pop findtext.chrg.cpMin
						.endif
						mov findtext.chrg.cpMax,-1
					.else
						mov findtext.chrg.cpMax,0
					.endif
					invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
					.if eax==BST_CHECKED
						or uFlags,FR_MATCHCASE
					.endif
					invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
					.if eax==BST_CHECKED
						or uFlags,FR_WHOLEWORD
					.endif
					mov findtext.lpstrText,offset FindBuffer
					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
					.if eax!=-1
						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
					.endif
				.endif
			.elseif ax==IDCANCEL
				invoke SendMessage,hWnd,WM_CLOSE,0,0
			.else
				mov eax,FALSE
				ret
			.endif
		.endif
	.elseif uMsg==WM_CLOSE
		mov hSearch,0
		invoke EndDialog,hWnd,0
	.else
		mov eax,FALSE
		ret
	.endif
	mov eax,TRUE
	ret
SearchProc endp

ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
	LOCAL settext:SETTEXTEX
	.if uMsg==WM_INITDIALOG
		push hWnd
		pop hSearch
		invoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer
		invoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer
	.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==IDOK
				invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
				invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer
				mov findtext.chrg.cpMin,0
				mov findtext.chrg.cpMax,-1
				mov findtext.lpstrText,offset FindBuffer
				mov settext.flags,ST_SELECTION
				mov settext.codepage,CP_ACP
				.while TRUE
					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
					.if eax==-1
						.break
					.else
						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
						invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
					.endif
				.endw
			.endif
		.endif
	.elseif uMsg==WM_CLOSE
		mov hSearch,0
		invoke EndDialog,hWnd,0
	.else
		mov eax,FALSE
		ret
	.endif
	mov eax,TRUE
	ret
ReplaceProc endp

GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
	LOCAL LineNo:DWORD
	LOCAL chrg:CHARRANGE
	.if uMsg==WM_INITDIALOG
		push hWnd
		pop hSearch
	.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==IDOK
				invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
				mov LineNo,eax
				invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
				.if eax>LineNo
					invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0
					mov chrg.cpMin,eax
					mov chrg.cpMax,eax
					invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
					invoke SetFocus,hwndRichEdit
				.endif
			.endif
		.endif
	.elseif uMsg==WM_CLOSE
		mov hSearch,0
		invoke EndDialog,hWnd,0
	.else
		mov eax,FALSE
		ret
	.endif
	mov eax,TRUE
	ret
GoToProc endp

PrepareEditMenu proc hSubMenu:DWORD
	LOCAL chrg:CHARRANGE
	invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
	.if eax==0		; no text in the clipboard
		invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED
	.else
		invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED
	.endif
	invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
	.if eax==0
		invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED
	.else
		invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED
	.endif
	invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
	.if eax==0
		invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED
	.else
		invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED
	.endif
	invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
	mov eax,chrg.cpMin
	.if eax==chrg.cpMax		; no current selection
		invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYED
		invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYED
		invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED
	.else
		invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLED
		invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLED
		invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED
	.endif
	ret
PrepareEditMenu endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
	LOCAL ofn:OPENFILENAME
	LOCAL buffer[256]:BYTE
	LOCAL editstream:EDITSTREAM
	LOCAL hFile:DWORD
	LOCAL hPopup:DWORD
	LOCAL pt:POINT
	LOCAL chrg:CHARRANGE
	.if uMsg==WM_CREATE
		invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
				CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
		mov hwndRichEdit,eax
		invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
		invoke SetColor
		invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
		invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
		invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
	.elseif uMsg==WM_NOTIFY
		push esi
		mov esi,lParam
		assume esi:ptr NMHDR
		.if [esi].code==EN_MSGFILTER
			assume esi:ptr MSGFILTER
			.if [esi].msg==WM_RBUTTONDOWN
				invoke GetMenu,hWnd
				invoke GetSubMenu,eax,1
				mov hPopup,eax
				invoke PrepareEditMenu,hPopup
				mov edx,[esi].lParam
				mov ecx,edx
				and edx,0FFFFh
				shr ecx,16
				mov pt.x,edx
				mov pt.y,ecx
				invoke ClientToScreen,hWnd,addr pt
				invoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL
			.endif
		.endif
		pop esi
	.elseif uMsg==WM_INITMENUPOPUP
		mov eax,lParam
		.if ax==0		; file menu			
			.if FileOpened==TRUE	; a file is already opened
				invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
			.else
				invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
			.endif
		.elseif ax==1	; edit menu
			invoke PrepareEditMenu,wParam
		.elseif ax==2		; search menu bar
			.if FileOpened==TRUE
				invoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED
			.else
				invoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED
			.endif
		.endif
	.elseif uMsg==WM_COMMAND
		.if lParam==0		; menu commands
			mov eax,wParam
			.if ax==IDM_OPEN
				invoke RtlZeroMemory,addr ofn,sizeof ofn
				mov ofn.lStructSize,sizeof ofn
				push hWnd
				pop ofn.hwndOwner
				push hInstance
				pop ofn.hInstance
				mov ofn.lpstrFilter,offset ASMFilterString
				mov ofn.lpstrFile,offset FileName
				mov byte ptr [FileName],0
				mov ofn.nMaxFile,sizeof FileName
				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
				invoke GetOpenFileName,addr ofn
				.if eax!=0
					invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
					.if eax!=INVALID_HANDLE_VALUE
						mov hFile,eax
						;================================================================
						; stream the text into the richedit control
						;================================================================						
						mov editstream.dwCookie,eax
						mov editstream.pfnCallback,offset StreamInProc
						invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
						;==========================================================
						; Initialize the modify state to false
						;==========================================================
						invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
						invoke CloseHandle,hFile
						mov FileOpened,TRUE
					.else
						invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
					.endif
				.endif
			.elseif ax==IDM_CLOSE
				invoke CheckModifyState,hWnd
				.if eax==TRUE
					invoke SetWindowText,hwndRichEdit,0
					mov FileOpened,FALSE
				.endif
			.elseif ax==IDM_SAVE
				invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
				.if eax!=INVALID_HANDLE_VALUE
@@:				
					mov hFile,eax
					;================================================================
					; stream the text to the file
					;================================================================						
					mov editstream.dwCookie,eax
					mov editstream.pfnCallback,offset StreamOutProc
					invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
					;==========================================================
					; Initialize the modify state to false
					;==========================================================
					invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
					invoke CloseHandle,hFile
				.else
					invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
				.endif
			.elseif ax==IDM_COPY
				invoke SendMessage,hwndRichEdit,WM_COPY,0,0
			.elseif ax==IDM_CUT
				invoke SendMessage,hwndRichEdit,WM_CUT,0,0
			.elseif ax==IDM_PASTE
				invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
			.elseif ax==IDM_DELETE
				invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
			.elseif ax==IDM_SELECTALL
				mov chrg.cpMin,0
				mov chrg.cpMax,-1
				invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
			.elseif ax==IDM_UNDO
				invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
			.elseif ax==IDM_REDO
				invoke SendMessage,hwndRichEdit,EM_REDO,0,0
			.elseif ax==IDM_OPTION
				invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
			.elseif ax==IDM_SAVEAS
				invoke RtlZeroMemory,addr ofn,sizeof ofn
				mov ofn.lStructSize,sizeof ofn
				push hWnd
				pop ofn.hwndOwner
				push hInstance
				pop ofn.hInstance
				mov ofn.lpstrFilter,offset ASMFilterString
				mov ofn.lpstrFile,offset AlternateFileName
				mov byte ptr [AlternateFileName],0
				mov ofn.nMaxFile,sizeof AlternateFileName
				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
				invoke GetSaveFileName,addr ofn
				.if eax!=0
					invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
					.if eax!=INVALID_HANDLE_VALUE
						jmp @B
					.endif
				.endif
			.elseif ax==IDM_FIND
				.if hSearch==0
					invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0
				.endif
			.elseif ax==IDM_REPLACE
				.if hSearch==0
					invoke CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0
				.endif
			.elseif ax==IDM_GOTOLINE
				.if hSearch==0
					invoke CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0
				.endif
			.elseif ax==IDM_FINDNEXT
				invoke lstrlen,addr FindBuffer
				.if eax!=0
					invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
					mov eax,findtext.chrg.cpMin
					.if eax!=findtext.chrg.cpMax
						push findtext.chrg.cpMax
						pop findtext.chrg.cpMin
					.endif
					mov findtext.chrg.cpMax,-1
					mov findtext.lpstrText,offset FindBuffer
					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
					.if eax!=-1
						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
					.endif
				.endif
			.elseif ax==IDM_FINDPREV
				invoke lstrlen,addr FindBuffer
				.if eax!=0
					invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
					mov findtext.chrg.cpMax,0
					mov findtext.lpstrText,offset FindBuffer
					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext
					.if eax!=-1
						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
					.endif
				.endif
			.elseif ax==IDM_EXIT
				invoke SendMessage,hWnd,WM_CLOSE,0,0
			.endif
		.endif
	.elseif uMsg==WM_CLOSE
		invoke CheckModifyState,hWnd
		.if eax==TRUE
			invoke DestroyWindow,hWnd
		.endif
	.elseif uMsg==WM_SIZE
		mov eax,lParam
		mov edx,eax
		and eax,0FFFFh
		shr edx,16
		invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE		
	.elseif uMsg==WM_DESTROY
		invoke PostQuitMessage,NULL
	.else
		invoke DefWindowProc,hWnd,uMsg,wParam,lParam		
		ret
	.endif
	xor eax,eax
	ret
WndProc endp
end start

分析

文本搜索功能是使用EM_FINDTEXTEX 来实现的。当用户点击Find菜单项时,IDM_FIND 消息就会被发送,并显示一个 搜索 对话框。


	invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
	.if eax!=0

当用户输入搜索正文,按下Ok按钮后,我们从FindBuffer缓冲区中得到要搜索的正文串。

		mov uFlags,0 
		invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

如果正文串不为空,我们就继续初始化uFlags 变量为 0 。这个变量被用来保存跟 EM_FINDTEXTEX 一起使用的搜索标志。之后,通过 EM_EXGETSEL ,我们得到当前选定的正文,因为我们需要知道搜索操作的开始位置。

	invoke IsDlgButtonChecked,hWnd,IDC_DOWN
		.if eax==BST_CHECKED
			or uFlags,FR_DOWN
			mov eax,findtext.chrg.cpMin
			.if eax!=findtext.chrg.cpMax
				push findtext.chrg.cpMax
				pop findtext.chrg.cpMin
			.endif
			mov findtext.chrg.cpMax,-1
		.else
			mov findtext.chrg.cpMax,0
		.endif

下一步就有一点精巧了。我们检查搜索方向Radio按钮来得到要按什么方向进行搜索。如果是向下搜索,我们设置 uFlagsFR_DOWN 标志值。然后我们比较 cpMincpMax,检查是否要在选定的正文里搜索。 如果两者的值不相等,说明有当前选定正文,我们需要从选定正文的结尾开始搜索,到控件中的正文的结尾结束。从而我们需要替换 cpMax 的值为 cpMin ,并改变 cpMax 的值为 -1 (0FFFFFFFFh)。如果没有当前选定正文,搜索的范围是从当前插入点(光标)到所有正文的结尾。

如果用户选择了向前搜索,我们使用的范围是从选定正文的开始到控件中正文的开始处。这个就是我们只改变 cpMax 的值为 0 原因。在使用向前搜索的情况下,cpMin 包含搜索范围中最后一个字符的的字符索引。而cpMax 则是搜索范围中第一个字符的字符索引。向后搜索则刚刚相反。

		invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
		.if eax==BST_CHECKED
			or uFlags,FR_MATCHCASE
		.endif
		invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
		.if eax==BST_CHECKED
			or uFlags,FR_WHOLEWORD
		.endif
		mov findtext.lpstrText,offset FindBuffer

我们继续检查搜索标志的检查框,也就是 FR_MATCHCASE and FR_WHOLEWORD。最后,我们把要搜索的正文串的偏移量放入 lpstrText 成员中。

		invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
		.if eax!=-1
			invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
		.endif
	.endif

现在我们已经准备好发送 EM_FINDTEXTEX 消息了。之后,我们检查通过 SendMessage 返回的搜索结果。如果返回 -1, 表示没有找到匹配的正文串。否则,FINDTEXTEX 结构的 chrgText 成员里会被填入匹配正文串的字符索引。因此我们继续使用 EM_EXSETSEL 消息来选定该正文串。

替换操作也是以差不多的方式来完成。

	invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
	invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer

我们先找到要搜索的正文串和用来替换的正文串。

	mov findtext.chrg.cpMin,0
	mov findtext.chrg.cpMax,-1
	mov findtext.lpstrText,offset FindBuffer

为容易起见,替换操作在整个控件的所有正文中进行。因此开始索引为 0 ,结束索引为 -1。

	mov settext.flags,ST_SELECTION
	mov settext.codepage,CP_ACP

我们初始化 SETTEXTEX 结构来表明我们想替换当前选定的文本和使用系统缺省的代码页。

	.while TRUE
		invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
		.if eax==-1
			.break
		.else
			invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
			invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
		.endif
	.endw

我们进入无限循环,来搜索匹配的正文。如果找到一个,我们就通过EM_EXSETSEL 来选定它,并通过EM_SETTEXTEX 替换它。当没找到其他匹配串时,我们就退出循环体。

Find Next Find Prev. 以跟Find操作相似方式使用 EM_FINDTEXTEX 消息实现的功能。 find operation.

下一步我们检查 Go to Line 功能。当用户点击 Go To Line 菜单项,我们显示如下的对话框:

用户输入行号并按下OK按钮后,我们就开始处理。

	invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
	mov LineNo,eax

从Edit控件中取得行号

	invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
	.if eax>LineNo

从Edit控件中取得行号并检查用户指定的行号是否超出发范围。

		invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0

如果是有效行号,我们要移动插入点到该行的第一个字符处。因此我们发送 EM_LINEINDEX 消息给RichEdit控件。这个消息返回指定行的第一个字母的字符索引。我们把行号放到wParam中发送消息,返回后我们就得到了该字符索引。

		invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eax

设置选择正文,这次我们使用 EM_SETSEL ,因为字符索引已经保存在 CHARRANGE 结构中,因而省了两个结构(要将那些索引放入一个 CHARRANGE 结构)。

		invoke SetFocus,hwndRichEdit
	.endif

除非RichEdit控件得到焦点,否则插入点将不会显示。因此我们调用 SetFocus 使它得到焦点.




翻译:farsky 来源:[Iczelion's Win32 Assembly Homepage]
LuoYunBin's Win32 ASM Page, http://asm.yeah.net