![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr Jerzy Wałaszek I LO w Tarnowie
|
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}
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.
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 ź.
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:
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.
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 ENDSFunkcja 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):
dwCookie: to samo, co w operacji wprowadzania strumienia. W tym parametrze zwykle przekazujemy uchwyt pliku, w którym chcemy umieścić zawartość kontrolki RichEdit.
pBuffer: wskazuje bufor udostępniony przez kontrolkę RichEdit, który jest wypełniony danymi z kontrolki.
NumBytes: rozmiar danych w buforze wskazywanym przez pBuffer.
pBytesTransferred: wskazuje na zmienną typu DWORD, w której musisz umieścić liczbę faktycznie odczytanych bajtów z bufora.
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.
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.1 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
}
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 ENDSPo 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. |
![]() | I Liceum Ogólnokształcące |
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