API и FreeBasic. (Операции над текстом в RichEdit)
В этом туториале вы узнаете больше о операциях над текстом, доступных в RichEdit, например о том, как искать/заменять текст и переходить к определенной строке.
Поиск текста
В RichEdit есть несколько текстовых операций. Поиск определенного текста - одна из них. Поиск текста осуществляется с помощью сообщений EM_FINDTEXT или EM_FINDTEXTEX. Эти сообщения мало отличаются друг от друга.
EM_FINDTEXT
wParam == опции поиска.
Может быть комбинацией значений, приведенных ниже. Эти опции идентичны как для EM_FINDTEXT, так и для EM_FINDTEXTEX.
- FR_DOWN - если этот флаг указан, поиск начинается с конца текущего выделенного текста и до самого конца (вперед). Этот флаг действует только в RichEdit 2.0 или выше: этот способ применяется по умолчанию в RichEdit 1.0. В RichEdit 2.0 по умолчанию поиск осуществляется от конца выделенного текста до начала (назад). Кратко говоря, если вы используете RichEdit 1.0, вы ничего не можете сделать, чтобы изменить направление поиска: он всегда ищет вперед. Если вы используете RichEdit 2.0 и хотите искать вперед, вам нужно указать этот флаг, иначе поиск будет производиться назад.
- FR_MATCHCASE - если этот флаг указан, будет учитываться регистр.
- FR_WHOLEWORD - если этот флаг указан, поиск будет искать место в тексте, которое будет удовлетворять указанной поисковой строке.
Вообще-то, есть еще несколько флагов, но они относятся к языкам, отличным от английского.
lParam == указатель на структуру FINDTEXT.
Type FINDTEXTA field=4 chrg As CHARRANGE lpstrText As LPSTR End Type
chrg - это структура CHARRANGE, которая определена следующим образом:
Type CHARRANGE field=4 cpMin As Long cpMax As Long End Type
cpMin содержит индекс первого символа в массиве символов (диапазон).
cpMax содержит индекс символа, который следует непосредственно за последним символов в массиве символов.
Фактически, чтобы найти строку текста, вам нужно указать диапазон символом, в котором нужно искать. Значение cpMin и cpMax будут зависеть от того, проводится ли поиск назад или вперед. Если поиск идет вперед, cpMin задает начальный индекс, а cpMax - конечный. Если поиск идет назад, тогда cpMin содержит конечное значение индекса, в то время как cpMax задает начальный индекс.
lpstrText - это указатель на текстовую строку, которую нужно искать.
EM_FINDTEXT возвращает индекс первого символа в заданной текстовой строке в RichEdit. Оно возвращает -1, если указанный текст не был найден.
EM_FINDTEXTEX
wParam == опции поиска. То же самое, что и EM_FINDTEXT. lParam == указатель на структуру FINDTEXTEX.
Type FINDTEXTEXA field=4 chrg As CHARRANGE lpstrText As LPSTR chrgText As CHARRANGE End Type
Первые два члена FINDTEXTEX идентичны соответствующим полям в структуре FINDTEXT. chrgText - это структура CHARRANGE, которая будет заполнена начальным и конечным индексами, если будут найдены какие-либо совпадения.
Возвращаемое значение EM_FINDTEXTEX - то же самое, что и у EM_FINDTEXT.
Разница между EM_FINDTEXT и EM_FINDTEXTEXT - это то, что у структуры FINDTEXTEX есть дополнительное поле, chrgText, которое будет заполнено начальным и конечным индексами, если будут найдены совпадения. Это удобно, если мы хотим иметь возможность осуществлять больше текстовых операций над строкой.
Замещение/вставка текста
Контрол RichEdit предоставляет EM_SETTEXTEX для замещения/вставки текста. Это сообщение комбинирует функциональность WM_SETTEXT и WM_REPLACESEL. У него следующий синтаксис:
EM_SETTEXTEXT wParam == указатель на структуру SETTEXTEX.
Type SETTEXTEX flags As DWORD codepage As UINT End Type
Поле 'flags' может быть комбинацией следующих значений:
- ST_DEFAULT - удаляет стек совершенных операций, сбрасывает форматирование RichEdit.
- ST_KEEPUNDO - сохраняет стек совершенных операций.
- ST_SELECTION - замещает выделенный текст и сохраняет форматирование.
Поле 'codepage' - это константа, которая указывает кодовую страницу. Обычно мы указываем CP_ACP.
Выделение текста
Мы можем выделить текст программно с помощью сообщений EM_SETSEL или EM_EXSETSEL. Обе прекрасно работают. Выбирать, какое из сообщений необходимо использовать, зависит от доступного формата индексов символов. Если они уже сохранены в структуры CHARRANGE, проще использовать EM_EXSETSEL.
EM_EXSETSEL
wParam == не используется. Должен быть равен 0. lParam == указатель на структуру CHARRANGE, который содержит диапазон символов, который необходимо выделить.
Уведомительные события
В случае с многолинейным edit control'ом вам необходимо сабклассировать его, чтобы получить входные сообщения, такие как события мыши/клавиатуры. RichEdit предоставляет лучший способ: он будет уведомлять родительское окно о таких событиях. Чтобы указать RichEdit, какие события нужно посылать, родительское окно посылает сообщение EM_SETEVENTMASK контролу RichEdit, указывая, в каких событиях он заинтересован. У EM_SETEVENTMASK следующий синтаксис:
EM_SETEVENTMASK
wParam == не используется. Должен быть равен нулю. lParam == маска событий. Это должна быть комбинация флагов, указанных ниже.
- ENM_CHANGE - прием уведомлений EN_CHANGE.
- ENM_CORRECTTEXT - прием уведомлений EN_CORRECTTEXT.
- ENM_DRAGDROPDONE - прием уведомлений EN_DRAGDROPDONE.
- ENM_DROPFILES - прием уведомлений EN_DROPFILES.
- ENM_KEYEVENTS - прием уведомлений EN_MSGFILTER, относящихся к событиям от клавиатуры.
- ENM_LINK - RichEdit 2.0 и выше: прием уведомлений EN_LINK, когда курсор мыши находится над текстом, который принимает CFE_LINK и/или другие события от мыши.
- ENM_MOUSEEVENTS - прием уведомлений EN_MSGFILTER, относящихся к событиям от мыши.
- ENM_OBJECTPOSITIONS - прием уведомлений EN_OBJECTPOSITIONS.
- ENM_PROTECTED - прием уведомлений EN_PROTECTED.
- ENM_REQUESTRESIZE - прием уведомлений EN_REQUESTRESIZE.
- ENM_SCROLL - прием уведомлений EN_HSCROLL и EN_VSCROLL.
- ENM_SCROLLEVENTS - прием уведомлений EN_MSGFILTER от колесика мыши.
- ENM_SELCHANGE - прием уведомлений EN_SELCHANGE.
- ENM_UPDATE - прием уведомлений EN_UPDATE. RichEdit 2.0 и выше: этот флаг игнорирует, а сообщения EN_UPDATE отсылаются всегда. Тем не менее, если Rich Edit 3.0 эмулирует Rich Edit 1.0, вы должны указать этот флаг для того, чтобы родительское окно принимало уведомления EN_UPDATE.
Все вышеуказанные уведомления будут отсылаться через сообщение WM_NOTIFY: вы должны проверить поле 'code' структуры NMHDR, чтобы узнать, какое уведомление вы получили. Например, если вы хотите зарегистрировать сообщения от мыши (скажем, чтобы отображать контекстное меню по нажатию на правую кнопку мыши), вы должны сделать что-то вроде следующего:
SendMessage(hwndRichEdit,EM_SETEVENTMASK, _ 0,ENM_MOUSEEVENTS) ..... ..... Function wndproc(hwnd As HWND, uMsg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer ..... .... Case WM_NOTIFY Dim nmhdr_ As NMHDR Ptr = Cast (NMHDR Ptr,lParam) If nmhdr_->code=EN_MSGFILTER Then .... [ Do something here] .... Endif
ПРИМЕР
Следующий пример является обновлением IczEdit. Были добавлены возможности поиска/замещения и акселераторы. Также обрабатываются события от мыши и отображается контекстное меню.
#INCLUDE Once "windows.bi" #INCLUDE Once "win/richedit.bi" #INCLUDE Once "win/commdlg.bi" Declare Function WinMain (Byval hInst As HINSTANCE, _ Byval hPrevInst As HINSTANCE, _ Byval szCmdLine As LPSTR, _ Byval iCmdShow As Integer ) As Integer Declare Function wndproc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Enum Iczelion IDM_OPEN=40001 IDM_SAVE IDM_CLOSE IDM_SAVEAS IDM_EXIT IDM_COPY IDM_CUT IDM_PASTE IDM_DELETE IDM_SELECTALL IDM_OPTION IDM_UNDO IDM_REDO IDR_MAINMENU=101 IDD_OPTIONDLG =101 IDD_FINDDLG IDD_GOTODLG IDD_REPLACEDLG IDR_MAINACCEL IDC_BACKCOLORBOX =1000 IDC_FINDEDIT =1000 IDC_TEXTCOLORBOX =1001 IDC_MATCHCASE =1001 IDC_REPLACEEDIT =1001 IDC_WHOLEWORD IDC_DOWN IDC_UP IDC_LINENO IDM_FIND =40014 IDM_FINDNEXT IDM_REPLACE IDM_GOTOLINE IDM_FINDPREV RichEditID =300 End Enum Dim Shared As String ClassName,AppName,RichEditDLL,RichEditClass, _ NoRichEdit,ASMFilterString,OpenFileFail,WannaSave, _ ASMSection,ZeroString ClassName = "IczEditClass" AppName = "IczEdit version 2.0" RichEditDLL = "riched20.dll" RichEditClass = "RichEdit20A" NoRichEdit = "Cannot find riched20.dll" ASMFilterString = "Text files (*.txt, *.ini, *.doc ,*.asm)"_ +Chr(0)+"*.txt;*.ini;*.doc;*.asm"+Chr(0) OpenFileFail = "Cannot open the file" WannaSave = "The data in the control is modified. Want to save it?" ASMSection = "ASSEMBLY" ZeroString = Chr(0) Dim Shared As Integer BackgroundColor = &hFFFFFF , _ TextColor, _ FileOpened, _ CustomColors(16) , _ uFlags Dim Shared As FINDTEXTEX FindText_ Dim Shared As HINSTANCE hInstance,hRichEdit Dim Shared As HACCEL hAccel Dim Shared As HWND hwndRichEdit,hSearch Dim Shared As String*256 _ FileName , _ AlternateFileName , _ FindBuffer , _ ReplaceBuffer '---------------Старт---------------------- hInstance=GetModuleHandle( NULL) hRichEdit = LoadLibrary( RichEditDLL) If hRichEdit<>0 Then WinMain(hInstance,0,0, SW_SHOWDEFAULT) FreeLibrary(hRichEdit) Else MessageBox(0,NoRichEdit,AppName,MB_OK Or MB_ICONERROR) Endif ExitProcess(0) Function WinMain (Byval hInst As HINSTANCE, _ Byval hPrevInst As HINSTANCE, _ Byval szCmdLine As LPSTR, _ Byval iCmdShow As Integer ) As Integer Dim As WNDCLASSEX wc Dim As MSG msg Dim As HWND hwnd wc.cbSize = Sizeof(WNDCLASSEX) wc.style = CS_HREDRAW Or CS_VREDRAW wc.lpfnWndProc = @wndproc wc.hInstance = hInst wc.hbrBackground = Cast(HBRUSH,COLOR_WINDOW+1) wc.lpszMenuName = Cast(LPSTR,IDR_MAINMENU) wc.lpszClassName = Strptr(ClassName) wc.hIcon = LoadIcon(NULL,IDI_APPLICATION) wc.hIconSm = wc.hIcon wc.hCursor = LoadCursor(NULL,IDC_ARROW) If RegisterClassEx(@wc)=0 Then Return 0 hwnd = CreateWindowEx(0,ClassName,AppName, _ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, _ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL, _ hInst,NULL) ShowWindow(hwnd,SW_SHOWNORMAL) UpdateWindow(hwnd) hAccel = LoadAccelerators(hInstance,Cast(LPCSTR,IDR_MAINACCEL)) While GetMessage(@msg,0,0,0) If IsDialogMessage(hSearch,@msg)=FALSE Then If TranslateAccelerator(hwnd,hAccel,@msg)=FALSE Then TranslateMessage(@msg) DispatchMessage(@msg) Endif Endif Wend End Function Function StreamInProc( hFile As Handle,pBuffer As PVOID, NumBytes As Integer, pBytesRead As Integer Ptr) As BOOl Dim As Integer length ReadFile(hFile,pBuffer,NumBytes,Cast(LPDWORD,@length),0) *pBytesRead = length If length = 0 Then Return 1 Endif End Function Function StreamOutProc (hFile As Handle,pBuffer As PVOID, NumBytes As Integer, pBytesWritten As Integer Ptr) As bool Dim As Integer length WriteFile(hFile,pBuffer,NumBytes,Cast(LPDWORD,@length),0) *pBytesWritten = length If length = 0 Then Return 1 Endif End Function Function CheckModifyState(hWnd As HWND) As BOOL If SendMessage(hwndRichEdit,EM_GETMODIFY,0,0)<>0 Then Select Case MessageBox(hWnd,WannaSave,AppName,MB_YESNOCANCEL) Case IDYES SendMessage(hWnd,WM_COMMAND,IDM_SAVE,0) Case IDCANCEL Return FALSE End Select Endif Return TRUE End Function Sub SetColor() Dim cfm As CHARFORMAT SendMessage(hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor) RtlZeroMemory(@cfm,Sizeof(cfm)) cfm.cbSize = Sizeof(cfm) cfm.dwMask = CFM_COLOR cfm.crTextColor = TextColor SendMessage(hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,Cast(LPARAM,@cfm)) End Sub Function OptionProc(hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer Dim clr As ChooseColor Select Case uMsg Case WM_INITDIALOG Case WM_COMMAND If Hiword(wParam) = BN_CLICKED Then Select Case Loword(wParam) Case IDCANCEL SendMessage(hWnd,WM_CLOSE,0,0) Case IDC_BACKCOLORBOX RtlZeroMemory(@clr,Sizeof(clr)) clr.lStructSize=SizeOf(clr) clr.hwndOwner = hWnd clr.hInstance = Cast(HWND,hInstance) clr.rgbResult = BackgroundColor clr.lpCustColors = @CustomColors(0) clr.Flags = CC_ANYCOLOR Or CC_RGBINIT If ChooseColor(@clr)<>0 Then BackgroundColor = clr.rgbResult InvalidateRect(GetDlgItem(hWnd,IDC_BACKCOLORBOX),0,TRUE) Endif Case IDC_TEXTCOLORBOX RtlZeroMemory(@clr,Sizeof(clr)) clr.lStructSize = Sizeof(clr) clr.hwndOwner = hWnd clr.hInstance = Cast(HWND,hInstance) clr.rgbResult = TextColor clr.lpCustColors = @CustomColors(0) clr.Flags = CC_ANYCOLOR Or CC_RGBINIT If ChooseColor(@clr)<>0 Then TextColor = clr.rgbResult InvalidateRect(GetDlgItem(hWnd,IDC_TEXTCOLORBOX),0,TRUE) Endif Case IDOK '================================================================================== ' Save the modify state of the richedit control because changing the text color changes the ' modify state of the richedit control. '================================================================================== Var GM = SendMessage(hwndRichEdit,EM_GETMODIFY,0,0) SetColor() SendMessage(hwndRichEdit,EM_SETMODIFY,GM,0) EndDialog(hWnd,0) End Select Endif Case WM_CTLCOLORSTATIC If GetDlgItem(hWnd,IDC_BACKCOLORBOX) = lParam Then Return Cast(Integer,CreateSolidBrush(BackgroundColor)) Else If GetDlgItem(hWnd,IDC_TEXTCOLORBOX)=lParam Then Return Cast(Integer,CreateSolidBrush(TextColor)) Endif Endif Return FALSE Case WM_CLOSE EndDialog(hWnd,0) Case Else Return FALSE End Select Return TRUE End Function Function SearchProc(hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer Select Case uMsg Case WM_INITDIALOG hSearch = hWnd '=================================================== ' Select the default search down option '=================================================== CheckRadioButton(hWnd,IDC_DOWN,IDC_UP,IDC_DOWN) SendDlgItemMessage(hWnd,IDC_FINDEDIT,WM_SETTEXT,0,Cast(LPARAM,@FindBuffer)) Case WM_COMMAND If Hiword(wParam) = BN_CLICKED Then Select Case Loword(wParam) Case IDOK uFlags = 0 SendMessage(hwndRichEdit,EM_EXGETSEL,0,Cast(LPARAM,@findtext_.chrg)) If GetDlgItemText(hWnd,IDC_FINDEDIT,Cast(LPSTR,@FindBuffer),Sizeof(FindBuffer))<>0 Then If IsDlgButtonChecked(hWnd,IDC_DOWN) = BST_CHECKED Then uFlags Or=FR_DOWN If findtext_.chrg.cpMin<>findtext_.chrg.cpMax Then findtext_.chrg.cpMin = findtext_.chrg.cpMax Endif findtext_.chrg.cpMax=-1 Else findtext_.chrg.cpMax=0 Endif If IsDlgButtonChecked(hWnd,IDC_MATCHCASE) = BST_CHECKED Then uFlags Or=FR_MATCHCASE Endif If IsDlgButtonChecked(hWnd,IDC_WHOLEWORD) = BST_CHECKED Then uFlags Or=FR_WHOLEWORD Endif findtext_.lpstrText = Strptr(FindBuffer) If SendMessage(hwndRichEdit,EM_FINDTEXTEX,uFlags,Cast(Lparam,@findtext_))<>-1 Then SendMessage(hwndRichEdit,EM_EXSETSEL,0,Cast(Lparam,@findtext_.chrgText)) Endif Endif Case IDCANCEL SendMessage(hWnd,WM_CLOSE,0,0) Case Else Return FALSE End Select Endif Case WM_CLOSE hSearch=0 EndDialog(hWnd,0) Case Else Return FALSE End Select Return TRUE End Function Function ReplaceProc(hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer Dim settext As SETTEXTEX Select Case uMsg Case WM_INITDIALOG hSearch = hWnd SetDlgItemText(hWnd,IDC_FINDEDIT,Cast(LPCTSTR,@FindBuffer)) SetDlgItemText(hWnd,IDC_REPLACEEDIT,Cast(LPCTSTR,@ReplaceBuffer)) Case WM_COMMAND If Hiword(wParam) = BN_CLICKED Then Select Case Loword(wParam) Case IDCANCEL SendMessage(hWnd,WM_CLOSE,0,0) Case IDOK GetDlgItemText(hWnd,IDC_FINDEDIT,Cast(LPCTSTR,@FindBuffer),Sizeof(FindBuffer)) GetDlgItemText(hWnd,IDC_REPLACEEDIT,Cast(LPCTSTR,@ReplaceBuffer),Sizeof(ReplaceBuffer)) findtext_.chrg.cpMin = 0 findtext_.chrg.cpMax = -1 findtext_.lpstrText = Strptr(FindBuffer) settext.flags = ST_SELECTION settext.codepage = CP_ACP While TRUE If SendMessage(hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,Cast(LPARAM,@FindText_)) = -1 Then Exit While Else SendMessage(hwndRichEdit,EM_EXSETSEL,0,Cast(LPARAM,@findtext_.chrgText)) SendMessage(hwndRichEdit,EM_SETTEXTEX,Cast(WPARAM,@settext),Cast(LPARAM,@ReplaceBuffer)) Endif Wend End Select Endif Case WM_CLOSE hSearch=0 EndDialog(hWnd,0) Case Else Return FALSE End Select Return TRUE End Function Function GoToProc (hWnd As HWND, uMsg As UINT, wParam As WPARAM, lParam As LPARAM) As Integer Dim LineNo As Integer Dim chrg As CHARRANGE Select Case uMsg Case WM_INITDIALOG hSearch = hWnd Case WM_COMMAND If Hiword(wParam) = BN_CLICKED Then Select Case Loword(wParam) Case IDCANCEL SendMessage(hWnd,WM_CLOSE,0,0) Case IDOK LineNo = GetDlgItemInt(hWnd,IDC_LINENO,NULL,FALSE) Var GM = SendMessage(hwndRichEdit,EM_GETLINECOUNT,0,0) If GM > LineNo Then Var GM = SendMessage(hwndRichEdit,EM_LINEINDEX,LineNo,0) SendMessage(hwndRichEdit,EM_SETSEL,GM,GM) SetFocus(hwndRichEdit) Endif End Select Endif Case WM_CLOSE hSearch = 0 EndDialog(hWnd,0) Case Else Return FALSE End Select Return TRUE End Function Function PrepareEditMenu(hSubMenu As HMENU) As Integer Dim chrg As CHARRANGE '============================================================================= ' Check whether there is some text in the clipboard. If so, we enable the paste menuitem '============================================================================= If SendMessage(hwndRichEdit,EM_CANPASTE,CF_TEXT,0)=0 Then EnableMenuItem(hSubMenu,IDM_PASTE,MF_GRAYED) Else EnableMenuItem(hSubMenu,IDM_PASTE,MF_ENABLED) Endif '========================================================== ' check whether the undo queue is empty '========================================================== If SendMessage(hwndRichEdit,EM_CANUNDO,0,0) = 0 Then EnableMenuItem(hSubMenu,IDM_UNDO,MF_GRAYED) Else EnableMenuItem(hSubMenu,IDM_UNDO,MF_ENABLED) Endif '========================================================= ' check whether the redo queue is empty '========================================================= If SendMessage(hwndRichEdit,EM_CANREDO,0,0) = 0 Then EnableMenuItem(hSubMenu,IDM_REDO,MF_GRAYED) Else EnableMenuItem(hSubMenu,IDM_REDO,MF_ENABLED) Endif '========================================================= ' check whether there is a current selection in the richedit control. ' If there is, we enable the cut/copy/delete menuitem '========================================================= SendMessage(hwndRichEdit,EM_EXGETSEL,0,Cast(Lparam,@chrg)) If chrg.cpMin = chrg.cpMax Then ' no current selection EnableMenuItem(hSubMenu,IDM_COPY,MF_GRAYED) EnableMenuItem(hSubMenu,IDM_CUT,MF_GRAYED) EnableMenuItem(hSubMenu,IDM_DELETE,MF_GRAYED) Else EnableMenuItem(hSubMenu,IDM_COPY,MF_ENABLED) EnableMenuItem(hSubMenu,IDM_CUT,MF_ENABLED) EnableMenuItem(hSubMenu,IDM_DELETE,MF_ENABLED) Endif Return 0 End Function Function wndproc(hwnd As HWND, uMsg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Dim ofn As OPENFILENAME Dim editstream As EDITSTREAM Dim hFile As HANDLE Dim hPopup As HMENU Dim pt As Point Dim chrg As CHARRANGE Select Case uMsg Case WM_CREATE hwndRichEdit = CreateWindowEx(WS_EX_CLIENTEDGE,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,Cast(HMENU,RichEditID),hInstance,0) ' Set the text limit. The default is 64K '============================================================= SendMessage(hwndRichEdit,EM_LIMITTEXT,-1,0) '============================================================= ' Set the default text/background color '============================================================= SetColor() SendMessage(hwndRichEdit,EM_SETMODIFY,FALSE,0) '============================================================ ' set event mask '============================================================ SendMessage(hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS) SendMessage(hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0) Case WM_NOTIFY Dim nmhdr_ As NMHDR Ptr = Cast (NMHDR Ptr,lParam) If nmhdr_->code=EN_MSGFILTER Then Dim msgfilter_ As MSGFILTER Ptr = Cast (MSGFILTER Ptr,lParam) If msgfilter_->msg=WM_RBUTTONDOWN Then hPopup = GetSubMenu(GetMenu(hWnd),1) PrepareEditMenu(hPopup) pt.x = Loword(msgfilter_->lParam) pt.y = Hiword(msgfilter_->lParam) ClientToScreen(hWnd,@pt) TrackPopupMenu(hPopup,TPM_LEFTALIGN Or TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL) Endif Endif Case WM_INITMENUPOPUP Select Case Loword(LPARAM) Case 0 If FileOpened=TRUE Then' a file is already opened EnableMenuItem(Cast(HMENU,wParam),IDM_OPEN,MF_GRAYED) EnableMenuItem(Cast(HMENU,wParam),IDM_CLOSE,MF_ENABLED) EnableMenuItem(Cast(HMENU,wParam),IDM_SAVE,MF_ENABLED) EnableMenuItem(Cast(HMENU,wParam),IDM_SAVEAS,MF_ENABLED) Else EnableMenuItem(Cast(HMENU,wParam),IDM_OPEN,MF_ENABLED) EnableMenuItem(Cast(HMENU,wParam),IDM_CLOSE,MF_GRAYED) EnableMenuItem(Cast(HMENU,wParam),IDM_SAVE,MF_GRAYED) EnableMenuItem(Cast(HMENU,wParam),IDM_SAVEAS,MF_GRAYED) Endif Case 1 ' edit menu PrepareEditMenu(Cast(HMENU,wParam)) Case 2 'search menu bar If FileOpened=TRUE Then EnableMenuItem(Cast(HMENU,wParam),IDM_FIND,MF_ENABLED) EnableMenuItem(Cast(HMENU,wParam),IDM_FINDNEXT,MF_ENABLED) EnableMenuItem(Cast(HMENU,wParam),IDM_FINDPREV,MF_ENABLED) EnableMenuItem(Cast(HMENU,wParam),IDM_REPLACE,MF_ENABLED) EnableMenuItem(Cast(HMENU,wParam),IDM_GOTOLINE,MF_ENABLED) Else EnableMenuItem(Cast(HMENU,wParam),IDM_FIND,MF_GRAYED) EnableMenuItem(Cast(HMENU,wParam),IDM_FINDNEXT,MF_GRAYED) EnableMenuItem(Cast(HMENU,wParam),IDM_FINDPREV,MF_GRAYED) EnableMenuItem(Cast(HMENU,wParam),IDM_REPLACE,MF_GRAYED) EnableMenuItem(Cast(HMENU,wParam),IDM_GOTOLINE,MF_GRAYED) Endif End Select Case WM_COMMAND If lParam=0 Then ' menu commands Select Case Loword(WPARAM) Case IDM_OPEN RtlZeroMemory(@ofn,Sizeof(ofn)) ofn.lStructSize = Sizeof(ofn) ofn.hwndOwner = hWnd ofn.hInstance = hInstance ofn.lpstrFilter = Strptr(ASMFilterString) ofn.lpstrFile = Strptr(FileName) FileName = "" ofn.nMaxFile = Sizeof(FileName) ofn.Flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or OFN_PATHMUSTEXIST If GetOpenFileName(@ofn)<>0 Then hFile = CreateFile(FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0) If hFile <> INVALID_HANDLE_VALUE Then '================================================================ ' stream the text into the richedit control '================================================================ editstream.dwCookie = Cast(DWORD,hFile) editstream.pfnCallback= Cast(EDITSTREAMCALLBACK,@StreamInProc) SendMessage(hwndRichEdit,EM_STREAMIN,SF_TEXT,Cast(LPARAM,@editstream)) '========================================================== ' Initialize the modify state to false '========================================================== SendMessage(hwndRichEdit,EM_SETMODIFY,FALSE,0) CloseHandle(hFile) FileOpened = TRUE Else MessageBox(hWnd,OpenFileFail,AppName,MB_OK Or MB_ICONERROR) Endif Endif Case IDM_CLOSE If CheckModifyState(hWnd) = TRUE Then SetWindowText(hwndRichEdit,"") FileOpened = FALSE Endif Case IDM_SAVE hFile = CreateFile(FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0) If hFile<>INVALID_HANDLE_VALUE Then '================================================================ ' stream the text to the file '================================================================ editstream.dwCookie = Cast(DWORD,hFile) editstream.pfnCallback= Cast(EDITSTREAMCALLBACK,@StreamOutProc) SendMessage(hwndRichEdit,EM_STREAMOUT,SF_TEXT,Cast(LPARAM,@editstream)) '========================================================== ' Initialize the modify state to false '========================================================== SendMessage(hwndRichEdit,EM_SETMODIFY,FALSE,0) CloseHandle(hFile) Else MessageBox(hWnd,OpenFileFail,AppName,MB_OK Or MB_ICONERROR) Endif Case IDM_COPY SendMessage(hwndRichEdit,WM_COPY,0,0) Case IDM_CUT SendMessage(hwndRichEdit,WM_CUT,0,0) Case IDM_PASTE SendMessage(hwndRichEdit,WM_PASTE,0,0) Case IDM_DELETE SendMessage(hwndRichEdit,EM_REPLACESEL,TRUE,0) Case IDM_SELECTALL chrg.cpMin = 0 chrg.cpMax = -1 SendMessage(hwndRichEdit,EM_EXSETSEL,0,Cast(LPARAM,@chrg)) Case IDM_UNDO SendMessage(hwndRichEdit,EM_UNDO,0,0) Case IDM_REDO SendMessage(hwndRichEdit,EM_REDO,0,0) Case IDM_OPTION DialogBoxParam(hInstance,Cast(LPCTSTR,IDD_OPTIONDLG),hWnd,@OptionProc,0) Case IDM_SAVEAS RtlZeroMemory(@ofn,Sizeof(ofn)) ofn.lStructSize = Sizeof(ofn) ofn.hwndOwner = hWnd ofn.hInstance = hInstance ofn.lpstrFilter = Strptr(ASMFilterString) ofn.lpstrFile = Strptr(AlternateFileName) AlternateFileName = "" ofn.nMaxFile = Sizeof(AlternateFileName) ofn.Flags = OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY Or OFN_PATHMUSTEXIST If GetSaveFileName(@ofn)<>0 Then hFile = CreateFile(AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0) If hFile<>INVALID_HANDLE_VALUE Then '================================================================ ' stream the text to the file '================================================================ editstream.dwCookie = Cast(DWORD,hFile) editstream.pfnCallback= Cast(EDITSTREAMCALLBACK,@StreamOutProc) SendMessage(hwndRichEdit,EM_STREAMOUT,SF_TEXT,Cast(LPARAM,@editstream)) '========================================================== ' Initialize the modify state to false '========================================================== SendMessage(hwndRichEdit,EM_SETMODIFY,FALSE,0) CloseHandle(hFile) Endif Endif Case IDM_FIND If hSearch=0 Then CreateDialogParam(hInstance,Cast(LPCTSTR,IDD_FINDDLG),hWnd,@SearchProc,0) Endif Case IDM_REPLACE If hSearch=0 Then CreateDialogParam(hInstance,Cast(LPCTSTR,IDD_REPLACEDLG),hWnd,@ReplaceProc,0) Endif Case IDM_GOTOLINE If hSearch=0 Then CreateDialogParam(hInstance,Cast(LPCTSTR,IDD_GOTODLG),hWnd,@GoToProc,0) Endif Case IDM_FINDNEXT If lstrlen(Cast(LPCTSTR, @FindBuffer))<>0 Then SendMessage(hwndRichEdit,EM_EXGETSEL,0,Cast(LPARAM,@findtext_.chrg)) If findtext_.chrg.cpMin<>findtext_.chrg.cpMax Then findtext_.chrg.cpMin = findtext_.chrg.cpMax Endif findtext_.chrg.cpMax = -1 findtext_.lpstrText = Strptr(FindBuffer) If SendMessage(hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,Cast(LPARAM,@FindText_))<> -1 Then SendMessage(hwndRichEdit,EM_EXSETSEL,0,Cast(LPARAM,@findtext_.chrgText)) Endif Endif Case IDM_FINDPREV If lstrlen(Cast(LPCTSTR,@FindBuffer))<> 0 Then SendMessage(hwndRichEdit,EM_EXGETSEL,0,Cast(LPARAM,@findtext_.chrg)) findtext_.chrg.cpMax = 0 findtext_.lpstrText = Strptr(FindBuffer) If SendMessage(hwndRichEdit,EM_FINDTEXTEX,0,Cast(LPARAM,@findtext_))<>-1 Then SendMessage(hwndRichEdit,EM_EXSETSEL,0,Cast(LPARAM,@findtext_.chrgText)) Endif Endif Case IDM_EXIT SendMessage(hWnd,WM_CLOSE,0,0) End Select Endif Case WM_CLOSE If CheckModifyState(hWnd)=TRUE Then DestroyWindow(hWnd) Endif Case WM_SIZE MoveWindow(hwndRichEdit,0,0,Loword(LPARAM),Hiword(LPARAM),TRUE) Case WM_DESTROY PostQuitMessage(NULL) Case Else Return DefWindowProc(hWnd,uMsg,wParam,lParam) End Select Return 0 End Function
Файл ресурсов:
//#include "resource.h" #DEFINE IDC_NEXT 1003 #DEFINE IDR_MAINMENU 101 #DEFINE IDD_OPTIONDLG 101 #DEFINE IDD_FINDDLG 102 #DEFINE IDD_GOTODLG 103 #DEFINE IDD_REPLACEDLG 104 #DEFINE IDR_MAINACCEL 105 #DEFINE IDC_BACKCOLORBOX 1000 #DEFINE IDC_FINDEDIT 1000 #DEFINE IDC_TEXTCOLORBOX 1001 #DEFINE IDC_MATCHCASE 1001 #DEFINE IDC_REPLACEEDIT 1001 #DEFINE IDC_WHOLEWORD 1002 #DEFINE IDC_DOWN 1003 #DEFINE IDC_UP 1004 #DEFINE IDC_LINENO 1005 #DEFINE IDM_OPEN 40001 #DEFINE IDM_SAVE 40002 #DEFINE IDM_CLOSE 40003 #DEFINE IDM_SAVEAS 40004 #DEFINE IDM_EXIT 40005 #DEFINE IDM_COPY 40006 #DEFINE IDM_CUT 40007 #DEFINE IDM_PASTE 40008 #DEFINE IDM_DELETE 40009 #DEFINE IDM_SELECTALL 40010 #DEFINE IDM_OPTION 40011 #DEFINE IDM_UNDO 40012 #DEFINE IDM_REDO 40013 #DEFINE IDM_FIND 40014 #DEFINE IDM_FINDNEXT 40015 #DEFINE IDM_REPLACE 40016 #DEFINE IDM_GOTOLINE 40017 #DEFINE IDM_FINDPREV 40018 #DEFINE IDC_STATIC -1 IDR_MAINMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_OPEN MENUITEM "&Close", IDM_CLOSE MENUITEM "&Save", IDM_SAVE MENUITEM "Save &As", IDM_SAVEAS MENUITEM SEPARATOR MENUITEM "E&xit", IDM_EXIT End POPUP "&Edit" BEGIN MENUITEM "&Undo", IDM_UNDO MENUITEM "&Redo", IDM_REDO MENUITEM "&Copy", IDM_COPY MENUITEM "C&ut", IDM_CUT MENUITEM "&Paste", IDM_PASTE MENUITEM SEPARATOR MENUITEM "&Delete", IDM_DELETE MENUITEM SEPARATOR MENUITEM "Select &All", IDM_SELECTALL End POPUP "&Search" BEGIN MENUITEM "&Find...\tCtrl+F", IDM_FIND MENUITEM "Find &Next\tF3", IDM_FINDNEXT MENUITEM "Find &Prev.\tCtrl+F3", IDM_FINDPREV MENUITEM "&Replace..\tCtrl+R", IDM_REPLACE MENUITEM SEPARATOR MENUITEM "&Go To Line\tCtrl+G", IDM_GOTOLINE End MENUITEM "Options", IDM_OPTION End IDD_OPTIONDLG DIALOG DISCARDABLE 0, 0, 183, 54 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Options" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,137,7,39,14 PUSHBUTTON "Cancel",IDCANCEL,137,25,39,14 GROUPBOX "",IDC_STATIC,5,0,124,49 LTEXT "Background Color:",IDC_STATIC,20,14,60,8 LTEXT "",IDC_BACKCOLORBOX,85,11,28,14,SS_NOTIFY | WS_BORDER LTEXT "Text Color:",IDC_STATIC,20,33,35,8 LTEXT "",IDC_TEXTCOLORBOX,85,29,28,14,SS_NOTIFY | WS_BORDER End IDD_FINDDLG DIALOG DISCARDABLE 0, 0, 186, 54 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Find.." FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_FINDEDIT,42,3,94,12,ES_AUTOHSCROLL CONTROL "Match Case",IDC_MATCHCASE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,54,10 CONTROL "Whole Word",IDC_WHOLEWORD,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,37,56,10 CONTROL "Down",IDC_DOWN,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP, 83,27,35,10 CONTROL "Up",IDC_UP,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,83, 38,25,10 DEFPUSHBUTTON "OK",IDOK,141,3,39,12 PUSHBUTTON "Cancel",IDCANCEL,141,18,39,12 LTEXT "Find what:",IDC_STATIC,5,4,34,8 GROUPBOX "Direction",IDC_STATIC,70,18,64,32 End IDD_GOTODLG DIALOGEX 0, 0, 106, 30 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_TOOLWINDOW CAPTION "Go To Line" FONT 8, "MS Sans Serif", 0, 0, 0x1 BEGIN EDITTEXT IDC_LINENO,29,4,35,11,ES_AUTOHSCROLL | ES_NUMBER, WS_EX_CLIENTEDGE DEFPUSHBUTTON "OK",IDOK,70,4,31,11 PUSHBUTTON "Cancel",IDCANCEL,70,17,31,11 LTEXT "Line :",IDC_STATIC,8,5,18,8 End IDD_REPLACEDLG DIALOG DISCARDABLE 0, 0, 186, 33 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Replace" FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_FINDEDIT,51,3,84,12,ES_AUTOHSCROLL EDITTEXT IDC_REPLACEEDIT,51,17,84,11,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,142,3,39,11 PUSHBUTTON "Cancel",IDCANCEL,142,17,39,11 LTEXT "Find what:",IDC_STATIC,3,4,34,8 LTEXT "Replace with",IDC_STATIC,3,18,42,8 End IDR_MAINACCEL ACCELERATORS DISCARDABLE BEGIN "F", IDM_FIND, VIRTKEY, CONTROL, NOINVERT "G", IDM_GOTOLINE, VIRTKEY, CONTROL, NOINVERT "R", IDM_REPLACE, VIRTKEY, CONTROL, NOINVERT VK_F3, IDM_FINDNEXT, VIRTKEY, NOINVERT VK_F3, IDM_FINDPREV, VIRTKEY, CONTROL, NOINVERT End
АНАЛИЗ
Поиск текста реализуется с помощью EM_FINDTEXTEX. Когда пользователь кликает на пункте меню 'Find', отсылается сообщение IDM_FIND и отображается соответствующее диалоговое окно.
If GetDlgItemText(hWnd,IDC_FINDEDIT,Cast(LPSTR,@FindBuffer),Sizeof(FindBuffer))<>0 Then
Когда пользователь задает текст, который нужно найти, а затем нажимает кнопку 'OK', текст, который мы собираемся искать, оказывается в FindBuffer.
uFlags = 0
SendMessage(hwndRichEdit,EM_EXGETSEL,0,Cast(LPARAM,@findtext_.chrg))
Если текстовая строка не равняется NULL, мы устанавливаем значение переменной uFlags равной 0. Эта переменная используется для хранения флагов поиска, отсылаемых вместе с сообщением EM_FINDTEXTEX. После этого мы получаем текущий выделенный текст с помощью EM_EXGETSEL, потому что нам нужно знать, откуда будет производиться поиск.
If IsDlgButtonChecked(hWnd,IDC_DOWN) = BST_CHECKED Then uFlags Or=FR_DOWN If findtext_.chrg.cpMin<>findtext_.chrg.cpMax Then findtext_.chrg.cpMin = findtext_.chrg.cpMax Endif findtext_.chrg.cpMax=-1 Else findtext_.chrg.cpMax=0 Endif
Следующая часть несколько сложнее. Мы проверяем значение кнопки, задающей направление поиска. Если указан поиск вперед, мы задаем флаг FR_DOWN в uFlags. После этого мы проверяем, выделен ли сейчас какой-нибудь текст, проверяя значения cpMin и cpMax. Если оба значения не равны, это означает, что выделение существует, и мы должны продолжать поиск от конца текущего выделения до конца текста в контроле. Поэтому нам требуется заменить значение cpMax на значение cpMin, а cpMax приравнять к -1 (&hFFFFFFFF). если выделения нет, поиск будет производиться от текущей позиции курсора до конца текста.
Если пользователь выберет поиск назад, мы используем диапазон от начала выделения до начала текста в контроле. Поэтому на нужно только изменить значение cpMax на 0. В случае с поиском назад cpMin содержит индекс последнего символа в диапазоне поиска, а cpMax - индекс первого символа. Это инверсия поиска вперед.
If IsDlgButtonChecked(hWnd,IDC_MATCHCASE) = BST_CHECKED Then uFlags Or=FR_MATCHCASE Endif If IsDlgButtonChecked(hWnd,IDC_WHOLEWORD) = BST_CHECKED Then uFlags Or=FR_WHOLEWORD Endif findtext_.lpstrText = Strptr(FindBuffer)
Мы продолжаем проверку checkbox'ов, чтобы задать значения FR_MATCHCASE и FR_WHOLEWORD. Наконец, мы помещаем смещение текста, который нужно найти в поле lpstrText.
If SendMessage(hwndRichEdit,EM_FINDTEXTEX,uFlags,Cast(Lparam,@findtext_))<>-1 Then SendMessage(hwndRichEdit,EM_EXSETSEL,0,Cast(Lparam,@findtext_.chrgText)) Endif
Мы сейчас готовы послать сообщение EM_FINDTEXTEX. После этого мы проверяем результаты поиска, возвращенные SendMessage. Если возвращаемое значение равно -1, значит никаких совпадений не было найдено. В противном случае поле chrgText структуры FINDTEXTEX заполняется индексами совпавшего текста. Мы выделяем найденный текст с помощью EM_EXSETSEL.
Операция замещения делается подобным же образом.
GetDlgItemText(hWnd,IDC_FINDEDIT,Cast(LPCTSTR,@FindBuffer),Sizeof(FindBuffer)) GetDlgItemText(hWnd,IDC_REPLACEEDIT,Cast(LPCTSTR,@ReplaceBuffer),Sizeof(ReplaceBuffer))
Мы получаем текст, который нужно найти, и текст, который нужно заменить.
findtext_.chrg.cpMin = 0
findtext_.chrg.cpMax = -1
findtext_.lpstrText = Strptr(FindBuffer)
Чтобы сделать проще, операция замещения действует на весь текст в контроле. Поэтому начальный индекс равен 0, а конечный - -1.
settext.flags = ST_SELECTION settext.codepage = CP_ACP
Мы инициализируем структуру SETTEXTEX, чтобы задать то, что мы хотим заменить текущее выделение и использовать системную страницу кодировки по умолчанию.
While TRUE If SendMessage(hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,Cast(LPARAM,@FindText_)) = -1 Then Exit While Else SendMessage(hwndRichEdit,EM_EXSETSEL,0,Cast(LPARAM,@findtext_.chrgText)) SendMessage(hwndRichEdit,EM_SETTEXTEX,Cast(WPARAM,@settext),Cast(LPARAM,@ReplaceBuffer)) Endif Wend
Мы входим в бесконечный цикл и ищем совпадающий текст. Если он был найден, мы выбираем его с помощью EM_EXSETSEL и заменяем его EM_SETTEXTEX. Если больше совпадений не было найдено, мы выходим из цикла.
'Find Next' и 'Find Prev.' используют сообщение EM_FINDTEXTEX похожим образом.
Теперь мы рассмотрим 'Go to Line'. Когда пользователь кликает по этому пункту меню, мы отображаем диалоговое окно.
Когда пользователь набирает номер линии и нажимет кнопку 'Ok', мы начинаем операцию.
LineNo = GetDlgItemInt(hWnd,IDC_LINENO,NULL,FALSE)
Получаем номер линии из edit control'а.
Var GM = SendMessage(hwndRichEdit,EM_GETLINECOUNT,0,0) If GM > LineNo Then
Получаем количество линий в контроле. Проверяем, не указал ли пользователь номер линии, который выходит за рамки допустимых значений.
Var GM = SendMessage(hwndRichEdit,EM_LINEINDEX,LineNo,0)
Если номер линии верен, мы передвигает курсор к первому символу в этой строке. Поэтому мы посылаем сообщение EM_LINEINDEX контролу richedit. Это сообщение возвращает индекс первого символа в заданной строке. Мы посылаем номер строки через wParam, а получаем индекс символа.
SendMessage(hwndRichEdit,EM_SETSEL,GM,GM)
Чтобы установить текущее выделение, в этот раз мы используем EM_SETSEL, потому что индексы символов уже не находятся в структуре CHARRANGE, что сохраняет нам две инструкции (помещение этих индексов в структуру CHARRANGE).
SetFocus(hwndRichEdit)
Курсор не будет отображен, пока контрол RichEdit не получит фокус. Поэтому мы вызываем SetFocus.
Перевод на русский с оригинала: Aquila, адаптация материалов под FreeBasic: Станислав Будинов
содержание | назад | вперед