Rozdział XXXV - RichEdit: Podświetlanie Składni


Przyjmij moje ostrzeżenie, zanim zaczniesz czytać ten rozdział. Poruszane tutaj tematy należą do trudnych i nie nadają się na materiał dla początkujących. Jest to ostatnia lekcja dotycząca kontrolki RichEdit.

 

obrazek

 

Załaduj {ten przykład}

 

Teoria

Podświetlanie składni (syntax hilighting) jest gorąco komentowane wśród twórców różnych edytorów tekstowych. Najlepszym rozwiązaniem (według mnie) jest zaprojektowanie własnej kontrolki edycyjnej i droga ta jest wybierana przez wiele komercyjnych firm programistycznych. Jednakże dla tych z nas, którym brakuje czasu na zaprogramowanie takiej kontrolki, następnym najlepszym rozwiązaniem jest takie zaadoptowanie istniejącej kontrolki, aby spełniała nasze potrzeby.

Spójrzmy na to, co udostępnia nam kontrolka RichEdit, aby nas wspomóż przy implementacji podświetlania składni. Od razu zastrzegam sobie, iż prezentowana metoda nie jest właściwa: po prostu chcę pokazać pułapki, w które wielu wpada. Kontrolka RichEdit udostępnia wiadomość EM_SETCHARFORMAT, którą można wykorzystać do zmiany koloru tekstu. Na pierwszy rzut oka wiadomość ta wydaje się idealnym rozwiązaniem (wiem to, bo sam kiedyś dałem się nabrać). Jednakże uważniejsze zbadanie jej ujawni kilka niepożądanych rzeczy:

Mając powyższe na względzie widzisz, że wykorzystanie EM_SETCHARFORMAT nie jest dobrym wyborem. Pokażę ci "względnie poprawny" wybór.

Metoda, z której obecnie korzystam, może być nazwana "podświetlanie składni na bieżąco". Podświetlam jedynie widoczną część tekstu. Stąd prędkość podświetlania w ogóle nie zależy od rozmiaru pliku. Bez względu na wielkość pliku tylko jego mała część jest w danym momencie widoczna.

 

Jak tego dokonać? Odpowiedź jest prosta:

  1. Przechwytujemy procedurę okna kontrolki i obsługujemy w naszej własnej procedurze okna wiadomości WM_PAINT.
  2. Gdy otrzymamy wiadomość WM_PAINT, wywołujemy oryginalną procedurę okna kontrolki RichEdit w celu jej obsługi i uaktualnienia swojego obszaru roboczego jak zwykle.
  3. Następnie zapisujemy słowa, które mają być podświetlone innym kolorem.

Oczywiście nie jest to aż tak proste: wciąż istnieje kilka ważnych elementów do rozwiązania, lecz powyższa metoda pracuje całkiem ładnie. Szybkość wyświetlania jest bardzo zadowalająca.

Teraz skoncentrujmy się na szczegółach. Proces przejmowania procedury okna jest prosty i nie wymaga wiele uwagi. Jedynym skomplikowanym elementem tej układanki jest znalezienie szybkiego sposobu wyszukiwania słów, które mają być podświetlane. Sprawa dalej się komplikuje, jeśli przyjmiemy, iż w obrębie komentarzy słowa te nie będą podświetlane (to samo powinno dotyczyć łańcuchów tekstu).

Metoda, z której korzystam, może nie być najlepsza, ale działa poprawnie. Na pewno znajdziesz szybszy sposób. W każdym razie oto ona:

WORDINFO STRUCT 
    WordLen  DD ? ; długość słowa do szybkiego porównania
    pszWord  DD ? ; wskazanie słowa 
    pColor   DD ? ; wskazanie koloru podświetlenia
    NextLink DD ? ; wskazanie następnej struktury WORDINFO
WORDINFO ENDS

Jak widać do drugiego szybkiego porównania stosuję długość słowa. Jeśli pierwszy znak wyrazu zgadza się z pierwszym znakiem podświetlanego słowa, porównujemy jego długość z długościami dostępnych słów. Każdy element tablicy ASMSyntaxArray zawiera wskazanie nagłówka związanej z nim struktury WORDINFO. Na przykład element reprezentujący znak "i" będzie zawierał wskazanie połączonej listy słów, które rozpoczynają się na literę "i". Pole pColor wskazuje zmienną DWORD zawierającą wartość koloru, który ma zostać użyty przy podświetlaniu słowa. Dlaczego nie umieszczamy tutaj po prostu koloru? Odpowiedź jest prosta. Pola te wskazują na elementy palety definiującej kolory dla kilku grup podświetlanych wyrazów. Jeśli będziemy chcieli zmienić w przyszłości kolory podświetleń, to wystarczy zmienić kolor w palecie dla danej grupy, a będzie on już automatycznie obowiązywał dla wszystkich wyrazów w tej grupie. Inaczej musielibyśmy wyszukiwać wszystkie wyrazy danej grupy i indywidualnie zmieniać im kolory - co najmniej kłopotliwe. Moje podejście jest zatem dużo lepsze i rokuje możliwość implementacji wyboru podświetleń dokonywanego przez użytkownika. Pole pszWord wskazuje słowo, które należy podświetlać, zapisane małymi literkami.

Lista słów jest umieszczona w pliku o nazwie "wordfile.txt" i uzyskuję do niej dostęp poprzez funkcję API GetPrivateProfileString. Udostępniam 10 różnych kolorów podświetleń, poczynając od C1 do C10. Tablica kolorów nosi nazwę ASMColorArray. Pole pColor każdej struktury WORDINFO wskazuje jeden z elementów DWORD tej tablicy. Zatem łatwo jest zmieniać kolory podświetlenia w locie, zmieniasz element DWORD w tablicy ASMColorArray, a wszystkie słowo używające tego koloru będą natychmiast stosowały nowy.

Przykład

Plik ICZEDIT.ASM

 

.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

WORDINFO STRUCT
    WordLen  DD ? ; długość słowa: używane do szybkiego porównywania
    pszWord  DD ? ; wskazanie słowa
    pColor   DD ? ; wskazanie koloru podświetlenia
    NextLink DD ? ; wskazanie następnej struktury WORDINFO
WORDINFO ENDS

.CONST

IDR_MAINMENU   EQU     101
IDD_OPTIONDLG  EQU     101
IDD_FINDDLG    EQU     102
IDD_GOTODLG    EQU     103
IDD_REPLACEDLG EQU     104
IDR_MAINACCEL  EQU     105

RichEditID     EQU     300

IDC_BACKCOLORBOX EQU   1000
IDC_TEXTCOLORBOX EQU   1001
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_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
IDM_FIND         EQU 40014
IDM_FINDNEXT     EQU 40015
IDM_REPLACE      EQU 40016
IDM_GOTOLINE     EQU 40017
IDM_FINDPREV     EQU 40018

.DATA

ClassName       DB "IczEditClass", 0
AppName         DB "IczEdit wersja 3.0", 0
RichEditDLL     DB "riched20.dll", 0
RichEditClass   DB "RichEdit20A", 0
NoRichEdit      DB "Nie można odnaleźć biblioteki riched20.dll", 0
ASMFilterString DB "Kod źródłowy ASM (*.asm)", 0, "*.asm", 0,
                   "Wszystkie pliki (*.*)", 0, "*.*", 0, 0
OpenFileFail    DB "Nie można otworzyć pliku", 0
WannaSave       DB "Dane w edytorze zostały zmodyfikowane. Zapisać je?", 0
FileOpened      DD FALSE
BackgroundColor DD 0FFFFFFh ; standardowy kolor tła - biały
TextColor       DD 0        ; standardowy kolor tekstu - czarny
WordFileName    DB "\wordfile.txt", 0
ASMSection      DB "ASSEMBLY", 0
C1Key           DB "C1", 0, 0
C2Key           DB "C2", 0, 0
C3Key           DB "C3", 0, 0
C4Key           DB "C4", 0, 0
C5Key           DB "C5", 0, 0
C6Key           DB "C6", 0, 0
C7Key           DB "C7", 0, 0
C8Key           DB "C8", 0, 0
C9Key           DB "C9", 0, 0
C10Key          DB "C10", 0
ZeroString      DB 0
ASMColorArray   DD 0FF0000h, 0805F50h, 0FFh, 666F00h, 44F0h,
                   5F8754h, 4 DUP(0FF0000h)
CmntColor       DD 808000h

.DATA?

hInstance         DD ?
hRichEdit         DD ?
hwndRichEdit      DD ?
FileName          DB 256 DUP(?)
AlternateFileName DB 256 DUP(?)
CustomColors      DD  16 DUP(?)
FindBuffer        DB 256 DUP(?)
ReplaceBuffer     DB 256 DUP(?)
uFlags            DD ?
findtext          FINDTEXTEX <>
ASMSyntaxArray    DD 256 DUP(?)
hSearch           DD ?          ; uchwyt okna dialogowego szukania/zamiany
hAccel            DD ?
hMainHeap         DD ?          ; uchwyt stosu
OldWndProc        DD ?
RichEditVersion   DD ?

.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 GetProcessHeap
        mov    hMainHeap, eax

; Ładujemy słowa, które będą podświetlane

        call   FillHiliteInfo
        INVOKE WinMain, hInstance, 0, 0, SW_SHOWDEFAULT
        INVOKE FreeLibrary, hRichEdit
    .ELSE
        INVOKE MessageBox, 0, ADDR NoRichEdit, ADDR AppName,
                           MB_OK OR MB_ICONERROR
    .ENDIF
    INVOKE ExitProcess, eax

WinMain PROC hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD

LOCAL wc   : WNDCLASSEX
LOCAL msg  : MSG
LOCAL hwnd : DWORD

    mov    wc.cbSize, SIZEOF WNDCLASSEX
    mov    wc.style, CS_HREDRAW OR CS_VREDRAW
    mov    wc.lpfnWndProc, OFFSET WndProc
    mov    wc.cbClsExtra, NULL
    mov    wc.cbWndExtra, NULL
    push   hInst
    pop    wc.hInstance
    mov    wc.hbrBackground, COLOR_WINDOW+1
    mov    wc.lpszMenuName, IDR_MAINMENU
    mov    wc.lpszClassName, OFFSET ClassName
    INVOKE LoadIcon, NULL, IDI_APPLICATION
    mov    wc.hIcon, eax
    mov    wc.hIconSm, eax
    INVOKE LoadCursor, NULL, IDC_ARROW
    mov    wc.hCursor, eax
    INVOKE RegisterClassEx, ADDR wc
    INVOKE CreateWindowEx, NULL, ADDR ClassName, ADDR AppName,
                           WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
                           CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                           NULL, NULL, hInst, NULL
    mov    hwnd, eax
    INVOKE ShowWindow, hwnd, SW_SHOWNORMAL
    INVOKE UpdateWindow, hwnd
    INVOKE LoadAccelerators, hInstance, IDR_MAINACCEL
    mov    hAccel, eax

    .WHILE TRUE
        INVOKE GetMessage, ADDR msg, 0, 0, 0
        .BREAK .IF (!eax)
        INVOKE IsDialogMessage, hSearch, ADDR msg
        .IF eax==FALSE
            INVOKE TranslateAccelerator, hwnd, hAccel, ADDR msg
            .IF eax==0
                INVOKE TranslateMessage, ADDR msg
                INVOKE DispatchMessage, ADDR msg
            .ENDIF
        .ENDIF
    .ENDW

    mov  eax, msg.wParam
    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 ax, WORD PTR wParam + 2
        .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

; Zachowujemy stan modyfikacji kontrolki RichEdit, ponieważ zmiana
; koloru zmienia również stan modyfikacji

                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

; Standardowo wybieramy opcję poszukiwań w dół

        INVOKE CheckRadioButton, hWnd, IDC_DOWN, IDC_UP, IDC_DOWN
        INVOKE SendDlgItemMessage, hWnd, IDC_FINDEDIT, WM_SETTEXT, 0,
                                   ADDR FindBuffer
    .ELSEIF uMsg==WM_COMMAND
        mov ax, WORD PTR wParam + 2
        .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 ax, WORD PTR wParam + 2
        .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

; Sprawdzamy, czy teksty do znalezienia i zastąpienia są różne. Jeśli
; tak, to wykonujemy operację zastąpienia. W przeciwnym wypadku nic
; nie robimy. Sprawdzenie to jest konieczne, ponieważ może dojść do
; zablokowania aplikacji z uwagi na przyjęty sposób pracy tej procedury.

                INVOKE lstrcmp, ADDR FindBuffer, ADDR ReplaceBuffer
                .IF eax!=0
                    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 OR FR_MATCHCASE,
                                            ADDR findtext
                        .BREAK .IF eax==-1
                        INVOKE SendMessage, hwndRichEdit, EM_EXSETSEL, 0,
                                            ADDR findtext.chrgText
                        INVOKE SendMessage, hwndRichEdit, EM_SETTEXTEX,
                                            ADDR settext, ADDR ReplaceBuffer
                    .ENDW
                .ENDIF
            .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 ax, WORD PTR wParam + 2
        .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
                    INVOKE SendMessage, hwndRichEdit, EM_SETSEL, eax, eax
                    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

; Sprawdzamy, czy w schowku jest jakiś tekst.
; Jeśli tak, to uaktywniamy element menu Wklej

    INVOKE SendMessage, hwndRichEdit, EM_CANPASTE, CF_TEXT, 0
    .IF eax==0               ; w schowku brak tekstu
        INVOKE EnableMenuItem, hSubMenu, IDM_PASTE, MF_GRAYED
    .ELSE
        INVOKE EnableMenuItem, hSubMenu, IDM_PASTE, MF_ENABLED
     .ENDIF

; Sprawdzamy, czy kolejka cofnięć jest pusta

    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

; Sprawdzamy, czy kolejka ponowień jest pusta

    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

; Sprawdzamy, czy w kontrolce RichEdit istnieje bieżące zaznaczenie
; Jeśli tak, włączamy elementy menu Wytnij/Kopiuj/Usuń

   
    INVOKE SendMessage, hwndRichEdit, EM_EXGETSEL, 0, ADDR chrg
    mov    eax, chrg.cpMin
    .IF eax==chrg.cpMax    ; brak bieżącego wyboru
        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

ParseBuffer PROC USES edi esi hHeap:       DWORD,
                              pBuffer:     DWORD,
                              nSize:       DWORD,
                              ArrayOffset: DWORD,
                              pArray:      DWORD
LOCAL buffer[128]: BYTE
LOCAL InProgress:   DWORD

    mov    InProgress, FALSE
    lea    esi, buffer
    mov    edi, pBuffer
    INVOKE CharLower, edi
    mov    ecx, nSize

SearchLoop:
    or     ecx, ecx
    jz     Finished
    cmp    BYTE PTR [edi], " "
    je     EndOfWord
    cmp    BYTE PTR [edi], 9         ; tab
    je     EndOfWord
    mov    InProgress, TRUE
    mov    al, BYTE PTR [edi]
    mov    BYTE PTR [esi], al
    inc    esi

SkipIt:
    inc    edi
    dec    ecx
    jmp    SearchLoop

EndOfWord:
    cmp    InProgress, TRUE
    je     WordFound
    jmp    SkipIt

WordFound:
    mov    BYTE PTR [esi], 0
    push   ecx

; umieszczamy słowo w strukturze WORDINFO

    INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, SIZEOF WORDINFO
    push   esi
    mov    esi, eax

ASSUME esi:PTR WORDINFO

    INVOKE lstrlen, ADDR buffer
    mov    [esi].WordLen, eax
    push   ArrayOffset
    pop    [esi].pColor
    inc    eax
    INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
    mov    [esi].pszWord, eax
    mov    edx, eax
    INVOKE lstrcpy, edx, ADDR buffer
    mov    eax, pArray
    movzx  edx, BYTE PTR [buffer]
    shl    edx, 2           ; mnożenie przez 4
    add    eax, edx
    .IF DWORD PTR [eax]==0
        mov  DWORD PTR [eax], esi
    .ELSE
        push DWORD PTR [eax]
        pop  [esi].NextLink
        mov  DWORD PTR [eax], esi
    .ENDIF
    pop    esi
    pop    ecx
    lea    esi, buffer
    mov    InProgress, FALSE
    jmp    SkipIt

Finished:
    .IF InProgress==TRUE

; Umieszczamy słowo w strukturze WORDINFO
       
        INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, SIZEOF WORDINFO
        push   esi
        mov    esi, eax

ASSUME esi:PTR WORDINFO

        INVOKE lstrlen, ADDR buffer
        mov    [esi].WordLen, eax
        push   ArrayOffset
        pop    [esi].pColor
        inc    eax
        INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
        mov    [esi].pszWord, eax
        mov    edx, eax
        INVOKE lstrcpy, edx, ADDR buffer
        mov    eax, pArray
        movzx  edx, BYTE PTR [buffer]
        shl    edx, 2               ; mnożenie przez 4
        add    eax, edx
        .IF DWORD PTR [eax]==0
            mov DWORD PTR [eax], esi
        .ELSE
            push DWORD PTR [eax]
            pop   [esi].NextLink
            mov   DWORD PTR [eax], esi
        .ENDIF
        pop esi
    .ENDIF
    ret

ParseBuffer ENDP

FillHiliteInfo PROC USES edi

LOCAL buffer[1024]: BYTE
LOCAL pTemp:        DWORD
LOCAL BlockSize:    DWORD

; Zerujemy tablicę

    INVOKE RtlZeroMemory, ADDR ASMSyntaxArray, SIZEOF ASMSyntaxArray

; Odczytujemy ścieżkę dyskową tego egzemplarza aplikacji
   
    INVOKE GetModuleFileName, hInstance, ADDR buffer, SIZEOF buffer
    INVOKE lstrlen, ADDR buffer
    mov    ecx, eax
    dec    ecx
    lea    edi, buffer
    add    edi, ecx
    std
    mov    al, "\"
    repne  scasb
    cld
    inc    edi
    mov    BYTE PTR [edi], 0
    INVOKE lstrcat, ADDR buffer, ADDR WordFileName

; Sprawdzamy istnienie pliku

    INVOKE GetFileAttributes, ADDR buffer
    .IF eax!=-1

; Przydzielamy ze stosu blok pamięci na łańcuchy znakowe

        mov    BlockSize, 1024*10
        INVOKE HeapAlloc, hMainHeap, 0, BlockSize
        mov    pTemp, eax
        mov    ecx, OFFSET C1Key
       
@@:       
        push   ecx
        INVOKE GetPrivateProfileString, ADDR ASMSection, ecx,
                                        ADDR ZeroString, pTemp,
                                        BlockSize, ADDR buffer
        pop    ecx
        .IF eax!=0
            inc eax
            .IF eax==BlockSize       ; bufor jest za mały
                add    BlockSize, 1024*10
                push   ecx
                INVOKE HeapReAlloc, hMainHeap, 0, pTemp, BlockSize
                pop       ecx
                mov       pTemp, eax
                jmp       @B
            .ENDIF
            mov    edx, ecx
            sub    edx, OFFSET C1Key
            add    edx, OFFSET ASMColorArray
            push   ecx
            INVOKE ParseBuffer, hMainHeap, pTemp, eax, edx, ADDR ASMSyntaxArray
            pop    ecx
         .ENDIF
        add    ecx, 4
        cmp    ecx, 4 + OFFSET C10Key
        jne    @B
        INVOKE HeapFree, hMainHeap, 0, pTemp
  
     .ENDIF
   
    ret

FillHiliteInfo ENDP

NewRichEditProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD

LOCAL hdc:             DWORD
LOCAL hOldFont:        DWORD
LOCAL FirstChar:       DWORD
LOCAL rect:            RECT
LOCAL txtrange:        TEXTRANGE
LOCAL buffer[1024*10]: BYTE
LOCAL hRgn:            DWORD
LOCAL hOldRgn:         DWORD
LOCAL RealRect:        RECT
LOCAL pString:         DWORD
LOCAL BufferSize:      DWORD
LOCAL pt:              POINT

    .IF uMsg==WM_PAINT
        push   edi
        push   esi
        INVOKE HideCaret, hWnd
        INVOKE CallWindowProc, OldWndProc, hWnd, uMsg, wParam, lParam
        push   eax
        mov    edi, OFFSET ASMSyntaxArray
        INVOKE GetDC, hWnd
        mov    hdc, eax
        INVOKE SetBkMode, hdc, TRANSPARENT

; Tutaj podświetlamy składnię

        INVOKE SendMessage, hWnd, EM_GETRECT, 0, ADDR rect
        INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect

; Odczytujemy numer wiersza

        INVOKE SendMessage, hWnd, EM_LINEFROMCHAR, eax, 0
        INVOKE SendMessage, hWnd, EM_LINEINDEX, eax, 0
        mov    txtrange.chrg.cpMin, eax
        mov    FirstChar, eax
        INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect.right
        mov    txtrange.chrg.cpMax, eax
        push   rect.left
        pop    RealRect.left
        push   rect.top
        pop    RealRect.top
        push   rect.right
        pop    RealRect.right
        push   rect.bottom
        pop    RealRect.bottom
        INVOKE CreateRectRgn, RealRect.left, RealRect.top,
                              RealRect.right, RealRect.bottom
        mov    hRgn, eax
        INVOKE SelectObject, hdc, hRgn
        mov    hOldRgn, eax
        INVOKE SetTextColor, hdc, CmntColor

; Pobieramy widoczny tekst do bufora

        lea    eax, buffer
        mov    txtrange.lpstrText, eax
        INVOKE SendMessage, hWnd, EM_GETTEXTRANGE, 0, ADDR txtrange
        mov     esi, eax  ; esi == rozmiar tekstu
        .IF esi>0
            mov     BufferSize, eax

; Najpierw szukamy komentarza
           
            push    edi
            push    ebx
            lea     edi, buffer
            mov     edx, edi  ; używane jako punkt odniesienia
            mov     ecx, esi
            mov     al, ";"

ScanMore:
            repne   scasb
            je      NextSkip
            jmp     NoMoreHit

NextSkip:
            dec     edi
            inc     ecx
            mov     pString, edi
            mov     ebx, edi
            sub     ebx, edx
            add     ebx, FirstChar
            mov     txtrange.chrg.cpMin, ebx

; Szukamy końca wiersza lub końca bufora

            push    eax
            mov     al, 0Dh
            repne   scasb
            pop     eax

HiliteTheComment:
            .IF ecx>0
                mov BYTE PTR [edi-1], 0
             .ENDIF
            mov     ebx, edi
            sub     ebx, edx
            add     ebx, FirstChar
            mov     txtrange.chrg.cpMax, ebx
            pushad

; Teraz wyszukujemy w zakresie tabulacje

            mov     edi, pString
            mov     esi, txtrange.chrg.cpMax
            sub     esi, txtrange.chrg.cpMin ; esi zawiera długość bufora
            mov     eax, esi
            push    edi
           
            .WHILE eax>0
                .IF BYTE PTR [edi]==9
                    mov BYTE PTR [edi], 0
                .ENDIF
                inc edi
                dec eax
            .ENDW

            pop edi
           
            .WHILE esi>0
                .IF BYTE PTR [edi]!=0
                    INVOKE lstrlen, edi
                    push   eax
                    mov    ecx, edi
                    lea    edx, buffer
                    sub    ecx, edx
                    add    ecx, FirstChar
                   
                    .IF RichEditVersion==3
                        INVOKE SendMessage, hWnd, EM_POSFROMCHAR,
                                            ADDR rect, ecx
                    .ELSE
                        INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ecx, 0
                        mov    ecx, eax
                        and    ecx, 0FFFFh
                        mov    rect.left, ecx
                        shr    eax, 16
                        mov    rect.top, eax
                    .ENDIF

                    INVOKE DrawText, hdc, edi, -1, ADDR rect, 0
                    pop    eax
                    add    edi, eax
                    sub    esi, eax
                .ELSE
                    inc edi
                    dec esi
                .ENDIF           
            .ENDW

            mov    ecx, txtrange.chrg.cpMax
            sub    ecx, txtrange.chrg.cpMin
            INVOKE RtlZeroMemory, pString, ecx
            popad
            .IF ecx>0
                jmp ScanMore
            .ENDIF
NoMoreHit:
            pop ebx
            pop edi

; Gdy mamy za sobą komentarze, pozbądźmy się separatorów

            mov ecx, BufferSize
            lea esi, buffer

            .WHILE ecx>0
                mov al, BYTE PTR [esi]
                .IF al==" " || al==0Dh || al=="/" || al=="," ||\
                    al=="|" || al=="+" || al=="-" || al=="*" ||\
                    al=="&" || al=="<" || al==">" || al=="=" ||\
                    al=="(" || al==")" || al=="{" || al=="}" ||\
                    al=="[" || al=="]" || al=="^" || al==":" || al==9
                    mov BYTE PTR [esi], 0
                .ENDIF
                dec ecx
                inc esi
            .ENDW

; Rozpoczynamy wyszukiwanie

            lea esi, buffer
            mov ecx, BufferSize

            .WHILE ecx>0
                mov al, BYTE PTR [esi]
                .IF al!=0
                    push   ecx
                    INVOKE lstrlen, esi
                    push   eax
                    mov    edx, eax   ; edx zawiera długość łańcucha
                    movzx  eax, BYTE PTR [esi]
                    .IF al>="A" && al<="Z"
                        sub al, "A"
                        add al, "a"
                    .ENDIF
                    shl eax, 2
                    add eax, edi   ; edi wskazanie tablicy wskazań WORDINFO
                    .IF DWORD PTR [eax]!=0
                        mov eax, DWORD PTR [eax]

ASSUME eax:PTR WORDINFO

                        .WHILE eax!=0
                            .IF edx==[eax].WordLen
                                pushad
                                INVOKE lstrcmpi, [eax].pszWord, esi
                                .IF eax==0                               
                                    popad

; Podświetlamy słowo
                                    mov ecx, esi
                                    lea edx, buffer
                                    sub ecx, edx
                                    add ecx, FirstChar
                                    pushad
                                    .IF RichEditVersion==3
                                        INVOKE SendMessage, hWnd, EM_POSFROMCHAR,
                                                            ADDR rect, ecx
                                    .ELSE
                                        INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ecx, 0
                                        mov       ecx, eax
                                        and       ecx, 0FFFFh
                                        mov       rect.left, ecx
                                        shr       eax, 16
                                        mov       rect.top, eax
                                    .ENDIF
                                    popad
                                    mov       edx, [eax].pColor
                                    INVOKE SetTextColor, hdc, DWORD PTR [edx]
                                    INVOKE DrawText, hdc, esi, -1, ADDR rect, 0
                                    .BREAK
                                .ENDIF
                                popad
                            .ENDIF
                            push [eax].NextLink
                            pop   eax
                        .ENDW
                    .ENDIF
                    pop eax
                    pop ecx
                    add esi, eax
                    sub ecx, eax
                .ELSE
                    inc esi
                    dec ecx
                .ENDIF
            .ENDW
        .ENDIF
        INVOKE SelectObject, hdc, hOldRgn
        INVOKE DeleteObject, hRgn
        INVOKE SelectObject, hdc, hOldFont
        INVOKE ReleaseDC, hWnd, hdc
        INVOKE ShowCaret, hWnd
        pop       eax
        pop       esi
        pop       edi
        ret
    .ELSEIF uMsg==WM_CLOSE
        INVOKE SetWindowLong, hWnd, GWL_WNDPROC, OldWndProc       
    .ELSE
        INVOKE CallWindowProc, OldWndProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF

NewRichEditProc 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

; Sprawdzamy wersję kontrolki RichEdit

        INVOKE SendMessage, hwndRichEdit, EM_SETTYPOGRAPHYOPTIONS,
                            TO_SIMPLELINEBREAK, TO_SIMPLELINEBREAK
        INVOKE SendMessage, hwndRichEdit, EM_GETTYPOGRAPHYOPTIONS, 1, 1
        .IF eax==0  ; oznacza, iż ta wiadomość jest nieobecna
            mov RichEditVersion, 2
        .ELSE
            mov RichEditVersion, 3

; Włączamy emulację systemowej kontrolki edycyjnej, aby
; uaktualnienie kolorów nie zajmowało zbyt wiele czasu

            INVOKE SendMessage, hwndRichEdit, EM_SETEDITSTYLE,
                                SES_EMULATESYSEDIT, SES_EMULATESYSEDIT
        .ENDIF

; Przechwytujemy procedurę okna kontrolki RichEdit

        INVOKE SetWindowLong, hwndRichEdit, GWL_WNDPROC, ADDR NewRichEditProc
        mov    OldWndProc, eax

; Zmieniamy pojemność tekstu. Standardowo jest 64KB

        INVOKE SendMessage, hwndRichEdit, EM_LIMITTEXT, -1, 0

; Ustawiamy standardowe kolory tekstu i tła
       
        INVOKE SetColor
        INVOKE SendMessage, hwndRichEdit, EM_SETMODIFY, FALSE, 0

; Ustawiamy maskę zdarzeń
      
        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  ; menu Plik
            .IF FileOpened==TRUE  ; plik jest już otwarty
                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  ; menu Edycja
            INVOKE PrepareEditMenu, wParam
        .ELSEIF ax==2  ; menu Szukaj
            .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  ; polecenia menu
            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

; Strumień tekstu do kontrolki RichEdit

                        mov    editstream.dwCookie, eax
                        mov    editstream.pfnCallback, OFFSET StreamInProc
                        INVOKE SendMessage, hwndRichEdit, EM_STREAMIN,
                                            SF_TEXT, ADDR editstream

; Inicjujemy stan modyfikacji na 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

; Strumień tekstu do pliku

                    mov    editstream.dwCookie, eax
                    mov    editstream.pfnCallback, OFFSET StreamOutProc
                    INVOKE SendMessage, hwndRichEdit, EM_STREAMOUT,
                                        SF_TEXT, ADDR editstream

; Inicjujemy stan modyfikacji na 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
        movzx  eax, WORD PTR lParam
        movzx  edx, WORD PTR lParam + 2
        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

 

Plik zasobów ICZEDIT.RC jest identyczny jak w poprzedniej lekcji.

 

Analiza

Przed wywołaniem procedury WinMain wywołujemy FillHiliteInfo. Funkcja ta odczytuje zawartość pliku wordfile.txt i przegląda ją.

 

FillHiliteInfo PROC USES edi

LOCAL buffer[1024]: BYTE
LOCAL pTemp:        DWORD
LOCAL BlockSize:    DWORD

    INVOKE RtlZeroMemory, ADDR ASMSyntaxArray, SIZEOF ASMSyntaxArray
    INVOKE GetModuleFileName, hInstance, ADDR buffer, SIZEOF buffer
    INVOKE lstrlen, ADDR buffer
    mov    ecx, eax
    dec    ecx
    lea    edi, buffer
    add    edi, ecx
    std
    mov    al, "\"
    repne  scasb
    cld
    inc    edi
    mov    BYTE PTR [edi], 0
    INVOKE lstrcat, ADDR buffer, ADDR WordFileName

 

Tworzymy pełną ścieżkę do pliku wordfile.txt: zakładam, iż zawsze będzie on w tym samym katalogu co program.

 

    INVOKE GetFileAttributes, ADDR buffer
    .IF eax!=-1

 

Stosuję tę metodę do szybkiego sprawdzenia istnienia pliku.

 

        mov    BlockSize, 1024*10
        INVOKE HeapAlloc, hMainHeap, 0, BlockSize
        mov    pTemp, eax

 

Przydzielamy pamięć do przechowywania słów. Standardowo będzie to 10KB. Pamięć przydzielana jest ze standardowej sterty aplikacji.

 

        mov    ecx, OFFSET C1Key
        
@@:        
        push   ecx
        INVOKE GetPrivateProfileString, ADDR ASMSection, ecx,
                                        ADDR ZeroString, pTemp,
                                        BlockSize, ADDR buffer
        pop    ecx
        .IF eax!=0
            inc eax
            .IF eax==BlockSize    ; bufor jest za mały
                add    BlockSize, 1024*10
                push   ecx
                INVOKE HeapReAlloc, hMainHeap, 0, pTemp, BlockSize
                pop    ecx
                mov    pTemp, eax
                jmp    @B
            .ENDIF

 

Sprawdzamy, czy blok jest wystarczająco duży. Jeśli nie, to zwiększamy jego wielkość o 10KB, aż będzie wystarczający do przechowania wszystkich podświetlanych słów.

 

            mov    edx, ecx
            sub    edx, OFFSET C1Key
            add    edx, OFFSET ASMColorArray
            push   ecx
            INVOKE ParseBuffer, hMainHeap, pTemp, eax, edx,
                                ADDR ASMSyntaxArray
            pop    ecx
        .ENDIF
        add ecx, 4
        cmp ecx, 4 + OFFSET C10Key
        jne @B
        
        INVOKE HeapFree, hMainHeap, 0, pTemp
    .ENDIF
    ret

FillHiliteInfo ENDP

 

Do funkcji ParseBuffer przekazujemy uchwyt bloku pamięci, odczytane słowa, rozmiar danych odczytanych z pliku wordfile.txt, adres definicji koloru używanego do podświetlania tych wyrazów oraz adres tablicy ASMSyntaxArray.

Teraz zbadajmy, co robi ParseBuffer. Akceptuje ona jako parametr adres bufora zawierającego słowa, które mają być podświetlane, wyodrębnia pojedyncze wyrazy i umieszcza każdy z nich w tablicy struktur WORDINFO, do której jest szybki dostęp z tablicy wskazań ASMSyntaxArray.

 

ParseBuffer PROC USES edi esi hHeap:       DWORD,
                              pBuffer:     DWORD,
                              nSize:       DWORD,
                              ArrayOffset: DWORD,
                              pArray:      DWORD
LOCAL buffer[128]: BYTE
LOCAL InProgress:  DWORD

    mov    InProgress, FALSE

 

Zmienna InProgress jest znacznikiem stosowanym do zaznaczenia rozpoczęcia procesu przeglądania. Jeśli ma wartość FALSE, to nie napotkaliśmy jeszcze znaku, który nie jest separatorem.

 

    lea    esi, buffer
    mov    edi, pBuffer
    INVOKE CharLower, edi

 

Rejestr esi wskazuje na lokalny bufor zawierający wyszukane słowo z listy słów. Rejestr edi wskazuje na łańcuch listy słów. Aby uprościć sobie późniejsze wyszukiwanie, zamieniamy wszystkie literki w słowach na małe.

 

    mov    ecx, nSize

SearchLoop:

    or     ecx, ecx
    jz     Finished
    cmp    BYTE PTR [edi], " "
    je     EndOfWord
    cmp    BYTE PTR [edi], 9     ; tab
    je     EndOfWord

 

Przeglądamy całą listę słów w buforze w poszukiwaniu separatorów. Jeśli znajdziemy separator, to musimy określić, czy oznacza on początek, czy koniec słowa.

 

    mov    InProgress, TRUE
    mov    al, BYTE PTR [edi]
    mov    BYTE PTR [esi], al
    inc    esi

SkipIt:

    inc    edi
    dec    ecx
    jmp    SearchLoop

 

Jeśli przeglądany znak nie jest separatorem, kopiujemy go do bufora, aby utworzyć kompletne słowo i kontynuujemy przeszukiwanie.

 

EndOfWord:

    cmp    InProgress, TRUE
    je     WordFound
    jmp    SkipIt

 

Jeśli natrafimy na separator, sprawdzamy wartość znacznika InProgress. Jeśli zawiera on TRUE, możemy przyjąć, iż oznacza on koniec słowa i możemy przejść do wstawienia słowa bieżąco w buforze (wskazywanym przez rejestr esi) do struktury WORDINFO. Jeśli wartość znacznika wynosi FALSE, kontynuujemy przeszukiwanie, aż zostanie znaleziony znak nie będący separatorem.

 

WordFound:

    mov    BYTE PTR [esi], 0
    push   ecx
    INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, SIZEOF WORDINFO

 

Gdy zostanie odszukany koniec słowa, dołączamy do bufora znak o kodzie 0, aby tekst był łańcuchem ASCIIZ. Następnie przydzielamy dla tego słowa ze sterty blok pamięci o rozmiarze struktury WORDINFO.

 

    push   esi
    mov    esi, eax

ASSUME esi:PTR WORDINFO

    INVOKE lstrlen, ADDR buffer
    mov    [esi].WordLen, eax

 

Pobieramy długość słowa w lokalnym buforze i umieszczamy ją w polu WordLen struktury WORDINFO w celu późniejszego wykorzystania przy porównaniach.

 

    push   ArrayOffset
    pop    [esi].pColor

 

W polu pColor umieszczamy wskazanie koloru, który ma być używany do podświetlania tego słowa.

 

    inc    eax
    INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
    mov    [esi].pszWord, eax
    mov    edx, eax
    INVOKE lstrcpy, edx, ADDR buffer

 

Przydzielamy ze sterty pamięć do przechowywania samego słowa. W tym momencie struktura WORDINFO jest gotowa do umieszczenia jej na odpowiedniej, połączonej liście.

 

    mov    eax, pArray
    movzx  edx, BYTE PTR [buffer]
    shl    edx, 2      ; mnożenie przez 4
    add    eax, edx

 

Parametr pArray zawiera adres tablicy ASMSyntaxArray. Przesuwamy się do elementu, którego indeks jest równy kodowi pierwszego znaku słowa. Zatem umieszczamy kod pierwszego znaku w rejestrze edx, a następnie mnożymy go przez 4 (ponieważ każdy element tablicy ASMSyntaxArray zajmuje w pamięci 4 bajty). Do wyniku dodajemy adres początku tablicy ASMSyntaxArray. W rejestrze eax otrzymujemy adres odpowiedniego elementu.

 

    .IF DWORD PTR [eax]==0
        mov DWORD PTR [eax], esi
    .ELSE
        push DWORD PTR [eax]
        pop  [esi].NextLink
        mov  DWORD PTR [eax], esi
    .ENDIF

 

Sprawdzamy wartość elementu tablicy. Jeśli wynosi 0, to oznacza to, iż bieżąco na liście żadne słowo nie rozpoczyna się od tej literki. Zatem umieszczamy tam adres bieżącej struktury WORDINFO.

Jeśli element tablicy ma wartość różną od 0, to na liście jest przynajmniej jedno słowo rozpoczynające się od tej literki. Zatem wstawiamy naszą strukturę WORDINFO na początek listy i uaktualniamy pole NextLink, aby wskazywało następną strukturę WORDINFO, która poprzednio była na początku listy.

 

    pop esi
    pop ecx
    lea esi, buffer
    mov InProgress, FALSE
    jmp SkipIt

 

Po zakończeniu tej operacji rozpoczynamy następny cykl przeglądania, aż dotrzemy do końca bufora.

 

        INVOKE SendMessage, hwndRichEdit, EM_SETTYPOGRAPHYOPTIONS,
                            TO_SIMPLELINEBREAK, TO_SIMPLELINEBREAK
        INVOKE SendMessage, hwndRichEdit, EM_GETTYPOGRAPHYOPTIONS, 1, 1
        .IF eax==0        ; oznacza, iż ta wiadomość jest nieobecna
            mov RichEditVersion, 2
        .ELSE
            mov RichEditVersion, 3
            INVOKE SendMessage, hwndRichEdit, EM_SETEDITSTYLE,
                                SES_EMULATESYSEDIT, SES_EMULATESYSEDIT
        .ENDIF

 

Po utworzeniu kontrolki RichEdit musimy określić jej wersję. Krok ten jest konieczny, ponieważ wiadomość EM_POSFROMCHAR zachowuje się różnie dla wersji 2.0 i 3.0, a wiadomość EM_POSFROMCHAR jest krytyczna dla naszej procedury podświetlania. Nigdy nie spotkałem się z udokumentowanym sposobem sprawdzania wersji kontrolki RichEdit, zatem musiałem to zrobić po swojemu. W tym przypadku ustawiam opcję specyficzną dla wersji 3.0 i natychmiast odczytuję jej wartość. Jeśli ją dostanę, to zakładam, iż mam do czynienia z kontrolką RichEdit w wersji 3.0.

Przy kontrolce RichEdit 3.0 zmiana koloru czcionki dla dużego pliku zajmuje całkiem sporo czasu. Problem ten wydaje się dotyczyć jedynie wersji 3.0. Znalazłem jego obejście: zmuszamy kontrolkę do emulacji zachowań systemowej kontrolki edycyjnej wysyłając do niej wiadomość EM_SETEDITSTYLE.

Po otrzymaniu informacji o numerze wersji przechodzimy dalej do przejęcia procedury okna kontrolki RichEdit. Teraz przeanalizujemy procedurę okna dla tej kontrolki.

 

NewRichEditProc PROC hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    ...
    .IF uMsg==WM_PAINT
        push   edi
        push   esi
        INVOKE HideCaret, hWnd
        INVOKE CallWindowProc, OldWndProc, hWnd, uMsg, wParam, lParam
        push   eax

 

Obsługujemy wiadomość WM_PAINT. Najpierw ukrywamy kursor tekstowy, aby pozbyć się śmieci graficznych po wykonaniu podświetlenia. Po tej operacji przekazujemy wiadomość do oryginalnej procedury okna kontrolki RichEdit, aby odświeżyła sobie okno. Gdy funkcja CallWindowProc powróci, tekst jest odświeżony w swoich standardowych kolorach liter i tła.. Teraz mamy sposobność dokonania podświetlenia składni.

 

        mov    edi, OFFSET ASMSyntaxArray
        INVOKE GetDC, hWnd
        mov    hdc, eax
        INVOKE SetBkMode, hdc, TRANSPARENT

 

Umieszczamy adres tablicy ASMSyntaxArray w rejestrze edi. Następnie pobieramy uchwyt kontekstu urządzenia i ustawiamy tryb przezroczystego tła tekstu, aby zapisywany w oknie tekst nie zmieniał koloru tła.

 

        INVOKE SendMessage, hWnd, EM_GETRECT, 0, ADDR rect
        INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect
        INVOKE SendMessage, hWnd, EM_LINEFROMCHAR, eax, 0
        INVOKE SendMessage, hWnd, EM_LINEINDEX, eax, 0

 

Chcemy otrzymać widoczny tekst, zatem najpierw musimy pobrać wymiary formatującego prostokąta wysyłając wiadomość EM_GETRECT do kontrolki RichEdit. Gdy mamy ograniczający prostokąt w następnej kolejności znajdujemy indeks najbliższego znaku w lewym górnym narożniku prostokąta za pomocą wiadomości EM_CHARFROMPOS. Gdy już będziemy posiadać ten indeks znaku (pierwszy widoczny znak w kontrolce), możemy rozpocząć podświetlanie składni od tej pozycji. Jednakże efekt końcowy może nie być tak dobry jak w przypadku rozpoczęcia od pierwszego znaku wiersza. Z tego powodu muszę odczytać numer wiersza, w którym znajduje się pierwszy widoczny w kontrolce znak. Dokonuję tego wysyłając do niej wiadomość EM_LINEFROMCHAR. Aby otrzymać pierwszy znak w tym wierszu, wysyłam wiadomość EM_LINEINDEX.

 

        mov    txtrange.chrg.cpMin, eax
        mov    FirstChar, eax
        INVOKE SendMessage, hWnd, EM_CHARFROMPOS, 0, ADDR rect.right
        mov    txtrange.chrg.cpMax, eax

 

Po otrzymaniu indeksu pierwszego znaku, zapamiętujemy go w zmiennej FirstChar dla użytku w przyszłości. Następnie odczytujemy indeks ostatniego widocznego znaku wysyłając wiadomość EM_CHARFROMPOS wraz ze współrzędnymi w lParam dolnego prawego narożnika formatującego prostokąta.

 

        push   rect.left
        pop    RealRect.left
        push   rect.top
        pop    RealRect.top
        push   rect.right
        pop    RealRect.right
        push   rect.bottom
        pop    RealRect.bottom
        INVOKE CreateRectRgn, RealRect.left, RealRect.top,
                              RealRect.right, RealRect.bottom
        mov    hRgn, eax
        INVOKE SelectObject, hdc, hRgn
        mov    hOldRgn, eax

 

Podczas projektowania podświetlania składni zauważyłem występowanie efektu ubocznego tej metody. Jeśli kontrolka RichEdit posiada margines (możesz określić margines w kontrolce RichEdit za pomocą wiadomości EM_SETMARGINS), funkcja DrawText pisze poza jego granice. Stąd muszę utworzyć obszar obcinania o rozmiarze formatującego prostokąta wywołując funkcję CreateRectRgn. Wyjście funkcji GDI będzie ograniczone do tego obszaru.

Następnie musimy najpierw podświetlić komentarze i usunąć je sobie z drogi. Moja metoda polega na wyszukiwaniu znaku ";" i podświetlaniu tekstu kolorem komentarza aż do napotkania znaku końca wiersza. Nie będę tutaj analizował tej procedury, gdyż jest dosyć długa i skomplikowana. Wystarczy powiedzieć, iż po podświetleniu wszystkich komentarzy są one zastępowane w buforze zerami, co pozwoli pominąć słowa zawarte w komentarzach.

 

    mov ecx, BufferSize
    lea esi, buffer
    .WHILE ecx>0
        mov al, BYTE PTR [esi]
        .IF al==" " || al==0Dh || al=="/" || al=="," ||\
            al=="|" || al=="+" || al=="-" || al=="*" ||\
            al=="&" || al=="<" || al==">" || al=="=" ||\
            al=="(" || al==")" || al=="{" || al=="}" ||\
            al=="[" || al=="]" || al=="^" || al==":" || al==9
            mov BYTE PTR [esi], 0
        .ENDIF
        dec ecx
        inc esi
    .ENDW
 

Po pozbyciu się komentarzy oddzielamy słowa w buforze zastępując znaki separatorów znakiem o kodzie 0. W efekcie nie musimy się martwić znakami separatorów przy przetwarzaniu słów w buforze - będzie tylko jeden rodzaj separatora - NULL.

 

    lea esi, buffer
    mov ecx, BufferSize
    .WHILE ecx>0
        mov al, BYTE PTR [esi]
        .IF al!=0

 

Szukamy w buforze pierwszego znaku, który nie jest znakiem o kodzie 0 - czyli jest pierwszym znakiem wyrazu.

 

    push   ecx
    INVOKE lstrlen, esi
    push   eax
    mov    edx, eax

 

Odczytujemy długość tego słowa i umieszczamy ją w rejestrze edx

 

    movzx  eax, BYTE PTR [esi]
    .IF al>="A" && al<="Z"
        add al, 32
    .ENDIF

 

Zmieniamy znak na małą literkę (o ile jest to duża literka)

 

    shl eax, 2
    add eax, edi
    .IF DWORD PTR [eax]!=0

 

Następnie przechodzimy do odpowiedniego dla tego znaku elementu tablicy ASMSyntaxArray i sprawdzamy, czy element ten nie ma wartości 0. Jeśli ma, to możemy przejść do kolejnego słowa.

 

    mov eax, DWORD PTR [eax]

ASSUME eax:PTR WORDINFO

    .WHILE eax!=0
        .IF edx==[eax].WordLen

 

Jeśli wartość elementu nie wynosi zero, to wskazuje on na połączoną listę struktur WORDINFO. Przetwarzamy tę listę porównując długość słowa w lokalnym buforze z długością słowa w strukturze WORDINFO. To szybki test przed porównaniem słów. Powinien on zaoszczędzić nieco cykli procesora.

 

    pushad
    INVOKE lstrcmpi, [eax].pszWord, esi
    .IF eax==0
                                

Jeśli długość obu słów jest równa, to porównujemy je przy pomocy funkcji lstrcmpi.

 

    popad
    mov ecx, esi
    lea edx, buffer
    sub ecx, edx
    add ecx, FirstChar

 

Konstruujemy indeks znaku z adresu pierwszego znaku słowa do podświetlenia w buforze. Najpierw obliczamy jego względną pozycję od początku bufora, a następnie dodajemy indeks znakowy pierwszego widocznego znaku.

 

    pushad
    .IF RichEditVersion==3
        INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ADDR rect, ecx
    .ELSE
        INVOKE SendMessage, hWnd, EM_POSFROMCHAR, ecx, 0
        mov    ecx, eax
        and    ecx, 0FFFFh
        mov    rect.left, ecx
        shr    eax, 16
        mov    rect.top, eax
    .ENDIF
    popad

Po obliczeniu indeksu znakowego pierwszego znaku w podświetlanym słowie przechodzimy do otrzymania jego współrzędnych wysyłając wiadomość EM_POSFROMCHAR. Jednakże wiadomość ta jest różnie interpretowania przez kontrolki RichEdit w wersji 2.0 i 3.0. Dla kontrolki RichEdit 2.0 wParam jest wskazaniem do struktury POINT, która będzie wypełniona współrzędnymi, a lParam zawiera indeks znaku.

Jak widać przesłanie złych argumentów do EM_POSFROMCHAR może wywołać nieprzewidywalne skutki w twoim systemie. Dlatego zdecydowałem się rozróżniać wersję kontrolek RichEdit.

 

        mov    edx, [eax].pColor
        INVOKE SetTextColor, hdc, DWORD PTR [edx]
        INVOKE DrawText, hdc, esi, -1, ADDR rect, 0
        .BREAK
    .ENDIF
    popad

 

Po otrzymaniu współrzędnych początku ustawiamy kolor tekstu zgodny z definicją w strukturze WORDINFO. A następnie zapisujemy słowo na ekranie nowym słowem o innym kolorze.

I ostatnie uwagi - prezentowana metoda może zostać ulepszona na kilka różnych sposobów. Na przykład, pobieram tekst rozpoczynający się od pierwszego do ostatniego widocznego wiersza. Jeśli wiersze są bardzo długie, to może spaść wydajność z uwagi na przetwarzanie słów, które nie pojawią się na ekranie. Możesz to zoptymalizować odczytując jedynie widoczny na ekranie fragment tekstu wiersz po wierszu. Również algorytm wyszukiwania można ulepszyć po zastosowaniu bardziej efektywnej metody. Zrozum mnie dobrze - prezentowana na tej lekcji metoda podświetlania składni jest SZYBKA, lecz może być jeszcze SZYBSZA.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe