Rozdział XXXIII - RichEdit: Podstawy


Wiele osób domaga się materiałów szkoleniowych na temat kontrolek RichEdit. Na koniec sam zabawiałem się nimi dostatecznie długo, aby dojść do wniosku, iż mogę coś o nich powiedzieć. Materiał podzieliłem ze względu na jego dużą objętość na trzy odrębne lekcje. Zawarłem w nich prawie wszystko na temat tej ciekawej kontrolki, albo przynajmniej tyle, ile sam o niej wiem. Na tej lekcji dowiesz się, czym jest kontrolka RichEdit, jak ją utworzyć oraz jak załadować do niej dane oraz jak te dane odczytywać.

 

 

Załaduj {ten przykład}

 

Teoria

Kontrolka RichEdit może być potraktowana jak doprawiona kontrolka edycyjna. Posiada wiele cech brakujących w prostej kontrolce edycji, na przykład możliwość stosowania wielu krojów czcionek o różnych rozmiarach, wielopoziomowy system cofnięć lub powtórzeń wykonanych operacji, wyszukiwania i zastępowanie tekstu, osadzanie obiektów OLE, wspomaganie edycji myszką itp. Ponieważ kontrolka RichEdit ma tak wiele różnych cech, została umieszczona w osobnej bibliotece DLL. Oznacza to również, iż do jej wykorzystywania nie wystarcza jedynie wywołanie funkcji InitCommonControls, jak przy innych wspólnych kontrolkach. Aby załadować bibliotekę richedit20.dll, będziesz musiał skorzystać z funkcji LoadLibrary.

Problemem jest to, iż dotychczas istnieją trzy wersje kontrolki RichEdit (cztery, jeśli weźmiemy pod uwagę wersję 4.1 dl Windows XP). Są to wersje 1.0, 2.0 oraz 3.0. Poniższa tablica pokazuje nazwę biblioteki DLL dla każdej z wersji kontrolki.

 

Wersja DLL Nazwa
1.0 Riched32.dll RICHEDIT
2.0 Riched20.dll RICHEDIT20A
3.0 Riched20.dll RICHEDIT20A
4.1 Msftedit.dll RICHEDIT20A

 

Jak widać, wersje kontrolki RichEdit 2.0 i 3.0 używają tej samej nazwy pliku biblioteki DLL. Mają również taką samą nazwę klasy! Może to stwarzać problemy, jeśli chcesz użyć specyficznych własności kontrolki dla wersji 3.0. Jak dotąd nie udało mi się znaleźć żadnej oficjalnej metody rozróżnienia pomiędzy wersją 2.0 a 3.0. Jednakże istnieje obejście tego problemu, które sprawdza się w praktyce. Zaprezentuję je później.

 

.DATA

RichEditDLL DB "RichEd20.dll", 0
...

.DATA?

hRichEditDLL DD ?

.CODE

    INVOKE LoadLibrary, ADDR RichEditDLL
    mov    hRichEditDLL, eax
    ...
    INVOKE FreeLibrary, hRichEditDLL

 

Po załadowaniu się biblioteki DLL RichEdit rejestruje ona klasę okna RichEdit. Dlatego bezwzględnie należy ładować tę bibliotekę DLL przed utworzeniem kontrolki. Nazwy klas kontrolki RichEdit również się różnią. Teraz możesz sobie zadawać pytanie - której wersji kontrolki RichEdit powinienem używać? Użycie najnowszej wersji nie zawsze jest odpowiednim rozwiązaniem, gdy nie potrzebujesz dodatkowych właściwości. Zatem poniżej umieściłem tabelę, która pokazuje cechy udostępniane przez każdą z wersji kontrolki RichEdit.

 

Cecha 1.0 2.0 3.0
pasek wyboru
x
x
x
edycja Unicode
x
x
formatowanieznaków/paragrafów
x
x
x
wyszukiwanie tekstu
do przodu
w obu kierunkach
w obu kierunkach
osadzanie obiektów OLE
x
x
x
edycja przez przeciąganiei upuszczanie
x
x
x
cofanie/ponawianie
jeden poziom
wielopoziomowe
wielopoziomowe
automatyczne rozpoznawaniehiperłącza URL
x
x
wsparcie dla klawiszyakceleratorów
x
x
działanie bez okna
x
x
koniec wiersza
CR LF
tylko CR
tylko CR
może emulować
wersję1.0
Zoom
x
numerowanie paragrafów
x
prosta tabelka
x
style normalnei nagłówkowe
x
kolorowanie podkreśleń
x
ukryty tekst
x
skojarzenie kroju pisma
x

 

Powyższa tabela w żadnym razie nie wyczerpuje tematu. Wymienia jedynie ważne cechy poszczególnych implementacji kontrolek RichEdit.

 

Tworzenie kontrolki RichEdit

Po załadowaniu biblioteki DLL RichEdit możesz już wywołać funkcję CreateWindowEx w celu utworzenia tej kontrolki. W wywołaniu CreateWindowEx możesz zastosować stule kontrolki edycyjnej oraz wspólne style okna za wyjątkiem ES_LOWERCASE, ES_UPPERCASE i ES_OEMCONVERT.

 

.CONST

RichEditID  EQU 300

.DATA

RichEditDLL   DB "RichEd20.dll", 0
RichEditClass DB "RichEdit20A", 0
...

.DATA?

hRichEditDLL DD ?
hwndRichEdit DD ?

.CODE

    ...
    INVOKE LoadLibrary, ADDR RichEditDLL
    mov    hRichEditDLL, eax
    INVOKE CreateWindowEx, 0, ADDR RichEditClass, 
                           WS_VISIBLE OR ES_MULTILINE OR \
                           WS_CHILD OR WS_VSCROLL OR WS_HSCROLL,
                           CW_USEDEFAULT, CW_USEDEFAULT, 
                           CW_USEDEFAULT, CW_USEDEFAULT,
                           hWnd, RichEditID, hInstance, 0
    mov    hwndRichEdit, eax
    INVOKE SendMessage, hwndRichEdit, EM_SETLANGOPTIONS, 0, 0

 

Uwaga od tłumacza: ostatnią wiadomość EM_SETLANGOPTIONS dodałem, ponieważ bez tej operacji kontrolka RichEdit dosyć dziwnie obsługuje polskie teksty zmieniając czcionkę po wystąpieniu literki ń i przywracając ją po literkach ś i ź.

 

Ustawianie standardowego koloru tekstu i tła

Możesz mieć problemy z ustawianiem koloru tekstu i tła kontrolki edycyjnej. Problem ten jednakże został uleczony w kontrolce RichEdit. Aby ustawić kolor tła kontrolki, wysyłasz do niej wiadomość EM_SETBKGNDCOLOR. Wiadomość ta posiada następujące parametry:

Na przykład, jeśli chcesz ustawić kolor tła na czysto niebieski, wyślesz następujące polecenie:

 

    INVOKE SendMessage, hwndRichEdit, EM_SETBKGNDCOLOR, 0, 0FF0000h

 

W celu ustawienia koloru tekstu kontrolka RichEdit udostępnia dla nas kolejną nową wiadomość - EM_SETCHARFORMAT. Wiadomość ta steruje formatowaniem znaków w wybranym bloku tekstu lub całego tekstu w kontrolce i posiada następujące parametry:

CHARFORMATA STRUCT
    cbSize          DWORD ?
    dwMask          DWORD ?
    dwEffects       DWORD ?
    yHeight         DWORD ?
    yOffset         DWORD ?
    crTextColor     COLORREF ?
    bCharSet        BYTE ?
    bPitchAndFamily BYTE ?
    szFaceName      BYTE LF_FACESIZE DUP(?)
    _wPad2          WORD ?
CHARFORMATA ENDS

CHARFORMAT EQU <CHARFORMATA>

Z przeanalizowania tej struktury wynika, iż można zmieniać efekty tekstowe (wytłuszczenie, kursywę, wyróżnienie, podkreślenie), kolor tekstu (crTextColor) oraz krój/wielkość/zestaw czcionki. Wartym uwagi znacznikiem jest CFE_PROTECTED. Tekst z tym znacznikiem jest zaznaczony jako chroniony, co oznacza, iż w przypadku próby jego zmiany przez użytkownika, zostanie wygenerowana wiadomość powiadomienia EN_PROTECTED do okna nadrzędnego. A tam można zezwolić na tę zmianę lub nie.

CHARFORMAT2 dodaje więcej stylów tekstowych, takich jak waga kroju pisma, odstępy między znakami, kolor tła znaków, kerning itp. Jeśli ci nie są potrzebne te nowe style, po prostu korzystaj ze struktury CHARFORMAT.

Przy ustawianiu formatowania tekstu musisz przemyśleć zasięg tych zmian. Kontrolka RichEdit udostępnia pojęcie zasięgu tekstu. Każdy znak otrzymuje numer począwszy od wartości 0: pierwszy znak w kontrolce posiada numer identyfikacyjny ID 0, drugi znak ma numer 1 i tak dalej. Aby określić zasięg tekstu, musisz przekazać kontrolce RichEdit dwie liczby: numery ID pierwszego i ostatniego znaku w zakresie. Za pomocą wiadomości EM_SETCHARFORMAT możesz zastosować formatowanie na trzy różne sposoby:

  1. dla całego tekstu w kontrolce (SCF_ALL)
  2. dla tekstu bieżąco zaznaczonego (SCF_SELECTION)
  3. dla całego słowa bieżąco zaznaczonego (SCF_WORD lub SCF_SELECTION).

Pierwszy i drugi wybór jest prosty w zrozumieniu. Ostatni wymaga nieco wyjaśnień. Jeśli bieżące zaznaczenie pokrywa pokrywa jeden lub kilka znaków w słowie, ale nie całe słowo, określenie znacznika SCF_WORD+SCF_SELECTION stosuje formatowanie tekstu do całego słowa. Nawet jeśli nie zaznaczono żadnych znaków, a jedynie kursor został umieszczony wewnątrz słowa, to trzeci wybór spowoduje zastosowanie formatowania tekstu do tego całego słowa.

Aby skorzystać z wiadomości EM_SETCHARFORMAT, musisz wypełnić kilka pól struktury CHARFORMAT (lub CHARFORMAT2). Na przykład, jeśli chcesz ustawić kolor tekstu, wypełniasz strukturę CHARFORMAT następująco:

 

.DATA?

cf CHARFORMAT <>
...

.CODE
    ...
    mov cf.cbSize, SIZEOF cf
    mov cf.dwMask, CFM_COLOR
    mov cf.crTextColor, 0FF0000h
    INVOKE SendMessage, hwndRichEdit, EM_SETCHARFORMAT,
                        SCF_ALL, ADDR cf

 

Powyższy fragment kodu ustawia kolor tekstu kontrolki RichEdit na czysto niebieski. Jeśli w kontrolce nie ma tekstu przed wysłaniem wiadomości EM_SETCHARFORMAT, to później wprowadzony tekst będzie stosował formatowanie, które tą wiadomością zostało ustawione.

Ustawianie/zapisywanie tekstu

Ci, którzy są przyzwyczajeni do kontrolki edycyjnej, na pewno znają wiadomości WM_GETTEXT/WM_SETTEXT jako środki pobierania i wprowadzania tekstu do kontrolki. Metody te wciąż działają z kontrolką RichEdit, jednakże nie są zbytnio efektywne, gdy plik jest duży. Kontrolka edycyjna ogranicza ilość wprowadzanego do niej tekstu do 64KB, lecz kontrolka RichEdit nie posiada takich ograniczeń. Byłoby bardzo kłopotliwe przydzielanie bardzo dużego bloku pamięci (np 10MB lub więcej) w celu pobrania tekstu przy pomocy WM_GETTEXT. Kontrolka RichEdit oferuje zupełnie nowe rozwiązanie - strumieniowanie tekstu (text streaming).

Mówiąc prosto dostarczamy kontrolce RichEdit adres funkcji zwrotnej (callback function), a kontrolka będzie wywoływać tę funkcję przekazując jej adres bufora, gdy będzie gotowa na przyjęcie danych. Funkcja zwrotna wypełni bufor danymi, które mają zostać przesłane do kontrolki lub odczyta dane z bufora, a następnie zaczeka na kolejne wywołanie, aż operacja się zakończy. Sposób ten wykorzystywany jest zarówno do wprowadzania strumienia tekstu jak i do wyprowadzania go. Zobaczysz, że metoda ta jest bardziej efektywna: bufor dostarcza sama kontrolka RichEdit, zatem dane są dzielone na kawałki. Operacja obejmuje dwie wiadomości: EM_STREAMIN i EM_STREAMOUT, które posiadają identyczną składnię:

EDITSTREAM STRUCT 
    dwCookie    DWORD ? 
    dwError     DWORD ? 
    pfnCallback DWORD ? 
EDITSTREAM ENDS

Funkcja zwrotna posiada następującą definicję:

 

EditStreamCallback PROTO dwCookie:          DWORD,
                         pBuffer:           DWORD,
                         NumBytes:          DWORD,
                         pBytesTransferred: DWORD

 

Musisz utworzyć w swoim programie funkcję o powyższym prototypie i przesłać jej adres do wiadomości EM_STREAMIN lub EM_STREAMOUT poprzez strukturę EDITSTREAM.

Dla operacji wprowadzania strumienia (ustawianie tekstu w kontrolce RichEdit):

Dla operacji wyprowadzania strumienia (pobieranie tekstu z kontrolki RichEdit):

Funkcja zwrotna zwraca 0, aby poinformować o sukcesie i kontrolka RichEdit będzie kontynuowała wywoływanie tej funkcji, jeśli wciąż są dane do przesłania. Jeśli w trakcie tego procesu pojawi się jakiś błąd i zechcesz zakończyć tę operację, zwróć wartość różną od 0, a kontrolka RichEdit zrezygnuje z danych w buforze. Wartość ta zostanie umieszczona w polu dwError struktury EDITSTREAM, abyś mógł sprawdzić status operacji przesłania strumienia po powrocie z funkcji SendMessage.

Przykład

Poniższy przykład jest prostym edytorem, w którym możesz otworzyć plik źródłowy asm, dokonać jego edycji i zapisu na dysk. Wykorzystana została kontrolka RichEdit wersja 2.0 lub wyższa.

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

.CONST

IDR_MAINMENU     EQU   101
IDD_OPTIONDLG    EQU   101
IDC_BACKCOLORBOX EQU  1000
IDC_TEXTCOLORBOX EQU  1001
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
RichEditID       EQU   300

.DATA

ClassName       DB "IczEditClass", 0
AppName         DB "IczEdit wersja 1.0", 0
RichEditDLL     DB "riched20.dll", 0
RichEditClass   DB "RichEdit20A", 0
NoRichEdit      DB "Nie można odnaleźć 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 zostały zmodyfikowane. Chcesz je zapisać?", 0
FileOpened      DD FALSE
BackgroundColor DD 0FFFFFFh     ; standardowo na biało
TextColor       DD 0            ; standardowo na czarno

.DATA?

hInstance         DD ?
hRichEdit         DD ?
hwndRichEdit      DD ?
FileName          DB 256 DUP(?)
AlternateFileName DB 256 DUP(?)
CustomColors      DD  16 DUP(?)

.CODE

start:

    INVOKE GetModuleHandle, NULL
    mov    hInstance, eax
    INVOKE LoadLibrary, ADDR RichEditDLL
    .IF eax!=0
        mov       hRichEdit, eax
        INVOKE WinMain, hInstance, 0, 0, SW_SHOWDEFAULT
        INVOKE FreeLibrary, hRichEdit
    .ELSE
        INVOKE MessageBox, 0, ADDR NoRichEdit, ADDR AppName, MB_OK OR MB_ICONERROR
    .ENDIF
    INVOKE ExitProcess, eax
   
WinMain PROC hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD

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

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

    .WHILE TRUE
        INVOKE GetMessage, ADDR msg, 0, 0, 0
        .BREAK .IF (!eax)
        INVOKE TranslateMessage, ADDR msg
        INVOKE DispatchMessage, ADDR msg
    .ENDW

    mov eax, msg.wParam
    ret

WinMain ENDP

StreamInProc PROC hFile:      DWORD,
                  pBuffer:    DWORD,
                  NumBytes:   DWORD,
                  pBytesRead: DWORD

    INVOKE ReadFile, hFile, pBuffer, NumBytes, pBytesRead, 0
    xor eax, 1
    ret

StreamInProc ENDP

StreamOutProc PROC hFile:         DWORD,
                   pBuffer:       DWORD,
                   NumBytes:      DWORD,
                   pBytesWritten: DWORD

    INVOKE WriteFile, hFile, pBuffer, NumBytes, pBytesWritten, 0
    xor eax, 1
    ret

StreamOutProc ENDP

CheckModifyState PROC hWnd:DWORD

    INVOKE SendMessage, hwndRichEdit, EM_GETMODIFY, 0, 0
    .IF eax!=0
        INVOKE MessageBox, hWnd, ADDR WannaSave, ADDR AppName,
                           MB_YESNOCANCEL
        .IF eax==IDYES
            INVOKE SendMessage, hWnd, WM_COMMAND, IDM_SAVE, 0
        .ELSEIF eax==IDCANCEL
            mov eax, FALSE
            ret
        .ENDIF
    .ENDIF
    mov eax, TRUE
    ret

CheckModifyState ENDP

SetColor PROC

LOCAL cfm: CHARFORMAT

    INVOKE SendMessage, hwndRichEdit, EM_SETBKGNDCOLOR,
                        0, BackgroundColor
    INVOKE RtlZeroMemory, ADDR cfm, SIZEOF cfm
    mov    cfm.cbSize, SIZEOF cfm
    mov    cfm.dwMask, CFM_COLOR
    push   TextColor
    pop    cfm.crTextColor
    INVOKE SendMessage, hwndRichEdit, EM_SETCHARFORMAT, SCF_ALL, ADDR cfm
    ret

SetColor ENDP

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

LOCAL clr: CHOOSECOLOR

    .IF uMsg==WM_INITDIALOG

    .ELSEIF uMsg==WM_COMMAND
        mov eax, wParam
        shr eax, 16
        .IF ax==BN_CLICKED
            mov eax, wParam
            .IF ax==IDCANCEL
                INVOKE SendMessage, hWnd, WM_CLOSE, 0, 0
            .ELSEIF ax==IDC_BACKCOLORBOX
                INVOKE RtlZeroMemory, ADDR clr, SIZEOF clr
                mov    clr.lStructSize, SIZEOF clr
                push   hWnd
                pop    clr.hwndOwner
                push   hInstance
                pop    clr.hInstance
                push   BackgroundColor
                pop    clr.rgbResult
                mov    clr.lpCustColors, OFFSET CustomColors
                mov    clr.Flags, CC_ANYCOLOR OR CC_RGBINIT
                INVOKE ChooseColor, ADDR clr
                .IF eax!=0
                    push   clr.rgbResult
                    pop    BackgroundColor
                    INVOKE GetDlgItem, hWnd, IDC_BACKCOLORBOX
                    INVOKE InvalidateRect, eax, 0, TRUE
                .ENDIF
            .ELSEIF ax==IDC_TEXTCOLORBOX
                INVOKE RtlZeroMemory, ADDR clr, SIZEOF clr
                mov    clr.lStructSize, SIZEOF clr
                push   hWnd
                pop    clr.hwndOwner
                push   hInstance
                pop    clr.hInstance
                push   TextColor
                pop    clr.rgbResult
                mov    clr.lpCustColors, OFFSET CustomColors
                mov    clr.Flags, CC_ANYCOLOR OR CC_RGBINIT
                INVOKE ChooseColor, ADDR clr
                .IF eax!=0
                    push   clr.rgbResult
                    pop    TextColor
                    INVOKE GetDlgItem, hWnd, IDC_TEXTCOLORBOX
                    INVOKE InvalidateRect, eax, 0, TRUE
                .ENDIF
            .ELSEIF ax==IDOK

; Zapisz stan modyfikacji kontrolki richedit, ponieważ zmiana
; koloru tekstu zmienia również stan modyfikacji kontrolki

                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

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

LOCAL chrg:        CHARRANGE
LOCAL ofn:         OPENFILENAME
LOCAL buffer[256]: BYTE
LOCAL editstream:  EDITSTREAM
LOCAL hFile:       DWORD

    .IF uMsg==WM_CREATE
        INVOKE CreateWindowEx, WS_EX_CLIENTEDGE, ADDR RichEditClass, 0,
                               WS_CHILD OR WS_VISIBLE OR ES_MULTILINE OR \
                               WS_VSCROLL OR WS_HSCROLL OR ES_NOHIDESEL, \
                               CW_USEDEFAULT, CW_USEDEFAULT,
                               CW_USEDEFAULT, CW_USEDEFAULT, hWnd,
                               RichEditID, hInstance, 0
        mov hwndRichEdit, eax
        INVOKE SendMessage, hwndRichEdit, EM_SETLANGOPTIONS, 0,0

; Ustaw graniczny rozmiar tekstu. Standardowo jest 64KB

        INVOKE SendMessage, hwndRichEdit, EM_LIMITTEXT, -1, 0

; Ustaw standardowe kolory tekstu i tła

        INVOKE SetColor
        INVOKE SendMessage, hwndRichEdit, EM_SETMODIFY, FALSE, 0
        INVOKE SendMessage, hwndRichEdit, EM_EMPTYUNDOBUFFER, 0, 0
    .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

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

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

; sprawdź, czy kolejka cofnięć jest pusta

            INVOKE SendMessage, hwndRichEdit, EM_CANUNDO, 0, 0
            .IF eax==0
                INVOKE EnableMenuItem, wParam, IDM_UNDO, MF_GRAYED
            .ELSE
                INVOKE EnableMenuItem, wParam, IDM_UNDO, MF_ENABLED
            .ENDIF

; sprawdź, czy kolejka powtórzeń jest pusta

            INVOKE SendMessage, hwndRichEdit, EM_CANREDO, 0, 0
            .IF eax==0
                INVOKE EnableMenuItem, wParam, IDM_REDO, MF_GRAYED
            .ELSE
                INVOKE EnableMenuItem, wParam, IDM_REDO, MF_ENABLED
            .ENDIF

; sprawdź, czy w kontrolce richedit jest bieżący wybór tekstu.
; Jeśli tak, to uaktywniamy 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 tekstu
                INVOKE EnableMenuItem, wParam, IDM_COPY, MF_GRAYED
                INVOKE EnableMenuItem, wParam, IDM_CUT, MF_GRAYED
                INVOKE EnableMenuItem, wParam, IDM_DELETE, MF_GRAYED
            .ELSE
                INVOKE EnableMenuItem, wParam, IDM_COPY, MF_ENABLED
                INVOKE EnableMenuItem, wParam, IDM_CUT, MF_ENABLED
                INVOKE EnableMenuItem, wParam, IDM_DELETE, MF_ENABLED
            .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

; wprowadź strumień tekstu do kontrolki richedit

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

; zainicjuj 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

; prześlij 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_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 ICZEDIT.RC

 

#include "RESOURCE.H"

#define IDR_MAINMENU        101
#define IDD_OPTIONDLG       101
#define IDC_BACKCOLORBOX   1000
#define IDC_TEXTCOLORBOX   1001
#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

IDR_MAINMENU MENU DISCARDABLE 

{
    POPUP "&Plik"
    {
        MENUITEM "&Otwórz", IDM_OPEN
        MENUITEM "Za&mknij", IDM_CLOSE
        MENUITEM "&Zapisz", IDM_SAVE
        MENUITEM "Z&apisz Jako", IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "Za&kończ", IDM_EXIT
    }
    POPUP "&Edycja"
    {
        MENUITEM "&Cofnij", IDM_UNDO
        MENUITEM "&Powtórz", IDM_REDO
        MENUITEM "&Kopiuj", IDM_COPY
        MENUITEM "&Wytnij", IDM_CUT
        MENUITEM "Wk&lej", IDM_PASTE
        MENUITEM SEPARATOR
        MENUITEM "&Usuń", IDM_DELETE
        MENUITEM SEPARATOR
        MENUITEM "Z&aznacz Wszystko", IDM_SELECTALL
   
    }
    MENUITEM "&Opcje", IDM_OPTION
}


IDD_OPTIONDLG DIALOG DISCARDABLE 0, 0, 183, 54

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION |
      WS_SYSMENU | DS_CENTER
CAPTION "Opcje"
FONT 8, "MS Sans Serif"
{
    DEFPUSHBUTTON "OK", IDOK, 137, 7, 39, 14
    PUSHBUTTON    "Anuluj", IDCANCEL, 137, 25, 39, 14
    GROUPBOX      "", IDC_STATIC, 5, 0, 124, 49
    LTEXT         "Kolor Tła:", IDC_STATIC, 20, 14, 60, 8
    LTEXT         "", IDC_BACKCOLORBOX, 85, 11, 28, 14,
                  SS_NOTIFY | WS_BORDER
    LTEXT         "Kolor Tekstu:", IDC_STATIC, 20, 33, 60, 8
    LTEXT         "", IDC_TEXTCOLORBOX, 85, 29, 28, 14,
                  SS_NOTIFY | WS_BORDER
}

Analiza

Program najpierw ładuje bibliotekę DLL RichEdit, która w naszym przypadku znajduje się w pliku riched20.dll. Jeśli biblioteki tej nie można załadować, to program wraca do systemu Windows.

 

    INVOKE LoadLibrary, ADDR RichEditDLL
    .IF eax!=0
        mov    hRichEdit, eax
        INVOKE WinMain, hInstance, 0, 0, SW_SHOWDEFAULT
        INVOKE FreeLibrary, hRichEdit
    .ELSE
        INVOKE MessageBox, 0, ADDR NoRichEdit, ADDR AppName,
                           MB_OK OR MB_ICONERROR
    .ENDIF
    INVOKE ExitProcess, eax

 

Po załadowaniu z sukcesem biblioteki DLL przechodzimy do utworzenia normalnego okna, które będzie zawierało kontrolkę RichEdit utworzoną w sekcji obsługi wiadomości WM_CREATE:

 

    .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

 

Zwróć uwagę, iż w stylach kontrolki musi się znaleźć ES_MULTILINE, inaczej kontrolka będzie jednowierszowa.

 

    INVOKE SendMessage, hwndRichEdit, EM_SETLANGOPTIONS, 0,0
    INVOKE SendMessage, hwndRichEdit, EM_LIMITTEXT, -1, 0

 

Po utworzeniu kontrolki usuwamy wszystkie opcje językowe, co zapobiega niewłaściwemu wyświetlaniu tekstu polskiego. Następnie ustawiamy nowy limit tekstu. Standardowo kontrolka RichEdit może przechowywać i przetwarzać 64KB tekstu, tyle samo co zwykła kontrolka edycyjna. Musimy rozszerzyć ten limit, aby kontrolka pracowała z większymi plikami. W powyższym wierszu określam wartość -1, co jest równe szesnastkowo 0FFFFFFFFh, czyli bardzo dużo.

 

    INVOKE SetColor

 

Dalej ustawiamy kolory tekstu i tła. Ponieważ operacja ta może być wykonywana w innych częściach programu, umieściłem ją w funkcji o nazwie SetColor.

 

SetColor PROC

LOCAL cfm: CHARFORMAT

    INVOKE SendMessage, hwndRichEdit, EM_SETBKGNDCOLOR,
                        0, BackgroundColor

 

Ustawianie koloru tła jest operacją bezpośrednią: po prostu wysyłamy wiadomość EM_SETBKGNDCOLOR do kontrolki RichEdit. (Jeśli stosujesz wielowierszową kontrolkę edycyjną, musisz obsłużyć wiadomość WM_CTLCOLOREDIT). Standardowym kolorem tła jest kolor biały.

 

    INVOKE RtlZeroMemory, ADDR cfm, SIZEOF cfm
    mov    cfm.cbSize, SIZEOF cfm
    mov    cfm.dwMask, CFM_COLOR
    push   TextColor
    pop    cfm.crTextColor

 

Po ustawieniu koloru tła, wypełniamy pola struktury CHARFORMAT w celu ustawienia koloru tekstu. Zauważ, iż pole cbSize wypełniamy rozmiarem tej struktury, aby kontrolka RichEdit wiedziała, iż przesyłamy jej strukturę CHARFORMAT a nie CHARFORMAT2. W polu dwMask umieszczamy tylko jeden znacznik - CFM_COLOR, ponieważ chcemy jedynie ustawić kolor tekstu i pole crTextColor zostaje wypełnione wartością pożądanego koloru.

 

    INVOKE SendMessage, hwndRichEdit, EM_SETCHARFORMAT, SCF_ALL, ADDR cfm
    ret

SetColor ENDP

 

Po ustawieniu koloru musimy wyzerować znacznik modyfikacji tekstu oraz opróżnić bufor cofnięć po prostu dlatego, iż operacja zmiany koloru tekstu i tła jest odwracalna i zostaje zapamiętana w tym buforze. Aby to osiągnąć, wysyłamy wiadomość EM_EMPTYUNDOBUFFER.

 

    INVOKE SendMessage, hwndRichEdit, EM_SETMODIFY, FALSE, 0
    INVOKE SendMessage, hwndRichEdit, EM_EMPTYUNDOBUFFER, 0, 0

 

Gdy kontrolka RichEdit zostaje po raz pierwszy utworzona, nie określamy jej pozycji w oknie ani jej rozmiarów. Dzieje się tak dlatego, iż chcemy, aby wypełniła cały obszar roboczy okna nadrzędnego. Zmienimy jej rozmiar, gdy otrzymamy wiadomość o zmianie rozmiaru okna nadrzędnego.

 

    .ELSEIF uMsg==WM_SIZE
        movzx  eax, WORD PTR lParam
        movzx  edx, WORD PTR lParam + 2
        INVOKE MoveWindow, hwndRichEdit, 0, 0, eax, edx, TRUE

 

W powyższym fragmencie kodu używamy nowych wymiarów obszaru roboczego przekazanych w lParam do zmiany rozmiaru kontrolki RichEdit za pomocą funkcji MoveWindow.

Gdy użytkownik klika na elemencie menu Plik/Edycja, otrzymujemy wiadomość WM_INITMENUPOPUP, która pozwoli nam przygotować listę elementów menu zanim zobaczy ją użytkownik. Na przykład, jeśli plik został już otwarty w kontrolce RichEdit, to wyłączamy element menu Otwórz a włączamy wszystkie pozostałe elementy menu.

W przypadku elementu menu Plik korzystamy ze zmiennej FileOpened jako znacznika określającego, czy dany plik został już otwarty. Jeśli wartość tej zmiennej wynosi TRUE, to wiemy, iż plik jest otwarty.

 

    .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

 

Jak widać w zależności od zawartości tej zmiennej włączamy lub wyłączamy odpowiednie elementy menu.

W przypadku elementu menu Edycja musimy najpierw sprawdzić stan schowka i kontrolki RichEdit.

 

    .ELSEIF ax==1               ; menu Edycja
        INVOKE SendMessage, hwndRichEdit, EM_CANPASTE, CF_TEXT, 0
        .IF eax==0              ; nie ma tekstu w schowku
            INVOKE EnableMenuItem, wParam, IDM_PASTE, MF_GRAYED
         .ELSE
            INVOKE EnableMenuItem, wParam, IDM_PASTE, MF_ENABLED
        .ENDIF

 

Sprawdzamy, czy w schowku jest dostępny jakiś tekst wysyłając wiadomość EM_CANPASTE. Jeśli tak, to funkcja SendMessage zwróci wartość TRUE i uaktywniamy element menu Wklej. Jeśli nie, wyłączamy ten element.

 

    INVOKE SendMessage, hwndRichEdit, EM_CANUNDO, 0, 0
    .IF eax==0
        INVOKE EnableMenuItem, wParam, IDM_UNDO, MF_GRAYED
    .ELSE
        INVOKE EnableMenuItem, wParam, IDM_UNDO, MF_ENABLED
    .ENDIF

 

Następnie sprawdzamy, czy bufor cofnięć jest pusty wysyłając wiadomość EM_CANUNDO. Jeśli nie jest pusty, to funkcja SendMessage zwróci wartość TRUE, a my uaktywniamy element menu Cofnij.

 

    INVOKE SendMessage, hwndRichEdit, EM_CANREDO, 0, 0
    .IF eax==0
        INVOKE EnableMenuItem, wParam, IDM_REDO, MF_GRAYED
    .ELSE
        INVOKE EnableMenuItem, wParam, IDM_REDO, MF_ENABLED
    .ENDIF

 

Identycznie postępujemy z buforem powtórzeń operacji wysyłając do kontrolki RichEdit wiadomość EM_CANREDO. Jeśli w buforze coś jest, uaktywniamy element menu Powtórz.

 

    INVOKE SendMessage, hwndRichEdit, EM_EXGETSEL, 0, ADDR chrg
        mov eax, chrg.cpMin
        .IF eax==chrg.cpMax        ; brak bieżącego wyboru tekstu
            INVOKE EnableMenuItem, wParam, IDM_COPY, MF_GRAYED
            INVOKE EnableMenuItem, wParam, IDM_CUT, MF_GRAYED
            INVOKE EnableMenuItem, wParam, IDM_DELETE, MF_GRAYED
        .ELSE
            INVOKE EnableMenuItem, wParam, IDM_COPY, MF_ENABLED
            INVOKE EnableMenuItem, wParam, IDM_CUT, MF_ENABLED
            INVOKE EnableMenuItem, wParam, IDM_DELETE, MF_ENABLED
        .ENDIF
    .ENDIF

 

Na koniec sprawdzamy, czy istnieje bieżące zaznaczenie wysyłając wiadomość EM_EXGETSEL. Wiadomość ta wykorzystuje strukturę CHARRANGE zdefiniowaną następująco:

 

CHARRANGE STRUCT
    cpMin DWORD ?
    cpMax DWORD ?
CHARRANGE ENDS

Po powrocie wiadomości EM_EXGETSEL struktura CHARRANGE jest wypełniona indeksami pozycji zaznaczonego bloku tekstu. Jeśli nie istnieje bieżące zaznaczenie, pola cpMin i cpMax mają taką samą zawartość, a wtedy wyłączamy opcje menu Wytnij/Kopiuj/Usuń.

Gdy użytkownik kliknie opcję Otwórz, wyświetlamy okno dialogowe otwierania pliku i po wyborze w nim pliku otwieramy go i przesyłamy w strumieniu danych jego zawartość do kontrolki RichEdit.

 

    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
        mov    editstream.dwCookie, eax
        mov    editstream.pfnCallback, OFFSET StreamInProc
        INVOKE SendMessage, hwndRichEdit, EM_STREAMIN,
                            SF_TEXT, ADDR editstream

 

Po sukcesie otwarcia pliku za pomocą funkcji CreateFile wypełniamy strukturę EDITSTREAM przygotowując się do wysłania wiadomości EM_STREAMIN. Poprzez pole dwCookie wyślemy uchwyt do otwartego pliku, a w pfnCallback umieścimy adres naszej funkcji zwrotnej odczytu strumienia.

Sama procedura odczytu strumienia jest niezmiernie prosta.

 

StreamInProc PROC hFile:      DWORD,
                  pBuffer:    DWORD,
                  NumBytes:   DWORD,
                  pBytesRead: DWORD

    INVOKE ReadFile, hFile, pBuffer, NumBytes, pBytesRead, 0
    xor eax, 1
    ret

StreamInProc ENDP

 

Możesz sam zobaczyć, iż wszystkie parametry procedury zwrotnej odczytu strumienia pasują idealnie do parametrów funkcji ReadFile. A wartość zwrotna z funkcji ReadFile jest poddawana operacji sumy symetrycznej z 1, zatem jeśli wynosi 1 (sukces), to zostaje zamieniona na 0 i na odwrót.

 

    INVOKE SendMessage, hwndRichEdit, EM_SETMODIFY, FALSE, 0
    INVOKE CloseHandle, hFile
    mov    FileOpened, TRUE

 

Gdy nastąpi powrót z EM_STREAMIN, operacja przesłania strumienia jest już zakończona. W rzeczywistości powinniśmy sprawdzić wartość pola dwError struktury EDITSTREAM.

Kontrolka RichEdit (a również edycyjna) obsługuje znacznik informujący o zmianie jej zawartości. Możemy pobrać wartość tego znacznika wysyłając wiadomość EM_GETMODIFY do kontrolki. Funkcja SendMessage zwraca wartość TRUE, jeśli zawartość kontrolki została zmodyfikowana. Ponieważ wprowadzamy strumień tekstu do kontrolki, to jest to pewien rodzaj modyfikacji. Zatem musimy ustawić znacznik modyfikacji na FALSE wysyłając do kontrolki wiadomość EM_SETMODIFY z parametrem wParam = FALSE po wykonaniu operacji, aby kontrolka rejestrowała zmiany od nowa. Natychmiast zamykamy plik i ustawiamy zmienną FileOpened na TRUE w celu zaznaczenia, iż plik został otwarty.

Gdy użytkownik klika na opcję menu Zapisz/Zapisz Jako, korzystamy z wiadomości EM_STREAMOUT do wysłania zawartości kontrolki RichEdit do pliku. Podobnie do funkcji zwrotnej odczytującej strumień, funkcja zapisująca jest bardzo prosta. Pasuje idealnie do WriteFile.

Operacje tekstowe takie jak Wytnij/Wklej/Powtórz/Cofnij są łatwo implementowane przez wysłanie pojedynczej wiadomości do kontrolki RichEdit. Są to odpowiednio wiadomości WM_CUT, WM_COPY, WM_PASTE, WM_REDO i WM_UNDO.Operacje Usuń/Zaznacz Wszystko są wykonywane następująco:

 

    .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

 

Operacja usuwania wpływa na bieżący wybór. Wysyłamy wiadomość EM_REPLACESEL z pustym łańcuchem, a kontrolka RichEdit zastąpi nim (czyli usunie) bieżąco zaznaczony blok tekstu.

Operacja Zaznacz Wszystko jest wykonywana przez wysłanie wiadomości EM_EXSETSEL z polami cpMin = 0 i cpMax = -1, co daje w wyniku zaznaczenie całego dostępnego tekstu.

Gdy użytkownik wybiera element menu Opcje, wyświetlamy okienko dialogowe ukazujące bieżące kolory tekstu i tła.

Gdy użytkownik kliknie w jeden z prostokątów koloru, wyświetlone zostaje okno dialogowe wyboru koloru. Prostokąt koloru jest w rzeczywistości statyczną kontrolką z ustawionymi znacznikami SS_NOTIFY i WS_BORDER. Statyczna kontrolka ze znacznikiem SS_NOTIFY będzie wysyłała powiadomienia do swojego okna nadrzędnego z informacją o wykonywanych nad nią operacjach myszką, takich jak BN_CLICKED (STN_CLICKED). To cała sztuczka.

 

    .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

 

Gdy użytkownik kliknie na jeden z prostokątów, wypełniamy pola struktury CHOOSECOLOR i wywołujemy funkcję ChooseColor w celu wyświetlenia okienka dialogowego wyboru koloru. Gdy użytkownik wybierze w nim jakiś kolor, to wartość tego koloru zostaje zwrócona w polu rgbResult i zachowujemy go w odpowiedniej zmiennej - BackgroundColor dla tła i TextColor dla tekstu. Następnie wymuszamy przemalowanie prostokąta koloru wywołując funkcję InvalidateRect z jego uchwytem. Prostokąt koloru wysyła wiadomość WM_CTLCOLORSTATIC do swojego okna nadrzędnego.

 

    .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

 

Wewnątrz sekcji obsługi wiadomości WM_CTLCOLORSTATIC porównujemy uchwyt statycznej kontrolki przesłany w lParam z oboma uchwytami prostokątów koloru. Jeśli wartości będą się zgadzały, tworzymy nowy pędzel korzystając z koloru przechowywanego w odpowiedniej zmiennej i natychmiast wracamy. Statyczna kontrolka wykorzysta nowo utworzony pędzel do wymalowania swojego tła.

 

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.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



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

©2017 mgr Jerzy Wałaszek

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