Ten odcinek kursu Macro Assemblera poświęcony jest tworzeniu kontrolek, czyli przycisków, pól tekstowych, list wyboru itp.
Aby zająć się tworzeniem kontrolek musimy najpierw utworzyć okno. Zostało to dokładnie omówione w poprzedniej części kursu. Kod, który wtedy napisaliśmy posłuży nam dzisiaj jako baza dla nowego programu. Przyjrzyjmy się procedurze obsługi okna:
WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,0 .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp
Na razie mamy obsłużoną jedynie wiadomość WM_DESTROY, która jest wywoływana podczas niszczenia okna. Kontrolki, które mają się pojawić w oknie powinniśmy utworzyć gdy wystąpi zdarzenie WM_CREATE. Jak się zapewne domyślacie, ma to miejsce zaraz po utworzeniu samego okna, ale jeszcze przed wyświetleniem go na ekranie. Nie wiecie jeszcze, że każda kontrolka w Windows jest osobnym oknem. Wszystkie przyciski, pola itp. są oknami. Dlatego też do ich tworzenia będziemy używać znanej już wam funkcji CreateWindowEx, jednak z nieco innymi parametrami niż przy tworzeniu okna głównego. Przypominam parametry tej funkcji:
- DWORD dwExStyle – rozszerzony styl
- LPCTSTR lpClassName – wskaźnik do zarejestrowanej klasy
- LPCTSTR lpWindowName – tytuł
- DWORD dwStyle – styl
- int x – poziome położenie
- int y – pionowe położenie
- int nWidth – szerokość
- int nHeight – wysokość
- HWND hWndParent – uchwyt okna nadrzędnego
- HMENU hMenu – uchwyt do menu lub numer kontrolki
- HINSTANCE hInstance – uchwyt instancji
- LPVOID lpParam – to nie będzie nam potrzebne, używane przy tworzeniu MDI
Parametry dwExStyle (rozszerzony styl) oraz dwStyle (styl okna) należy dobrać zależnie od kontrolki, którą chcemy utworzyć, określają one dokładnie wygląd i zachowanie obiektu. Możemy tutaj na przykład określić pozycję tekstu wewnątrz przycisku lub rodzaj ramki wokół pola tekstowego. Nie należy zapominać o podaniu atrybutów WS_VISIBLE oraz WS_CHILD w polu dwStyle, które są niezbędne aby kontrolka została poprawnie utworzona i wyświetlona. Wszystkie możliwe style zostały opisane w Win32 Programmer’s Reference pod hasłem CreateWindow. Jeżeli zamierzamy połączyć kilka styli, używamy operatora „or”, np. „WS_VISIBLE or WS_CHILD or WS_BORDER”.
Jako lpClassName podajemy adres zmiennej tekstowej, która zawiera nazwę jednej z predefiniowanych klas. To ona określa jakiego typu kontrolka zostanie utworzona. I tak na przykład jeśli chcemy utworzyć przycisk, podajemy tutaj adres zmiennej zawierającej słowo „BUTTON”, jeśli pole tekstowe – „EDIT”. Inne możliwości są wymienione i opisane w Helpie (hasło w indeksie: CreateWindow).
W lpWindowName umieszczamy adres zmiennej zawierającej tytuł okna. Tekst ten pojawi się poźniej na przycisku lub wewnątrz pola tektowego.
W hWndParent musimy koniecznie podać uchwyt okna nadrzędnego – okna, w którym ma się znaleźć tworzona kontrolka. Ten uchwyt jest w procedurze WndProc zapisany w zmiennej hWnd.
Jako hMenu podajemy tzw. ID czyli unikalny numer kontrolki. Każdej kontrolce w oknie musimy przypisać osobne ID. Dzięki niemu będziemy mogli rozpoznać która kontrolka wygenerowała zdarzenie – np. jeżeli zostanie kliknięty przycisk, Windows podaje nam jego ID i dzieki temu możemy odpowiednio na to kliknięcie zareagować.
Myślę, że reszta parametrów jest zrozumiała, możemy więc przejść do praktyki. Utwórzmy na początek pole tekstowe.
WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,0 .ELSEIF uMsg==WM_CREATE invoke CreateWindowEx,\ WS_EX_CLIENTEDGE,\ ;rozszerzony styl, dzięki zastosowaniu WS_EX_CLIENTEDGE ;okno będzie miało trójwymiarową ramkę ADDR classEdit,\ ;adres zmiennej zawierającej nazwę klasy okna 0,\ ;tytuł okna - wpisując 0 pozostawiamy pole tekstowe puste WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or ES_AUTOHSCROLL,\ ;styl - wyjaśnienie w helpie 50,35,200,25,\ ;pozycja i wymiary okna hWnd,\ ;uchwyt okna nadrzędnego EditID,\ ;ID kontrolki wyrażony za pomocą stałej hInstance,\ ;uchwyt instancji 0 ;nie potrzebne, podajemy 0 mov hEdit,eax ;zapisanie uchwytu utworzonej kontrolki w zmiennej hEdit .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp
Musimy jeszcze zadeklarować zmienne i stałą, których nam brakuje.
.data classEdit db "EDIT",0
.data?
hEdit dd ?
.const
EditID equ 2
Zamiast stosować jako ID nic nie mówiącą liczbę 2, stosujemy zamiast niej stałą, której nazwa może być dowolna (w tym przypadku jest to EditID). Kompilator zamienia każde wystąpienie stałej na odpowiadającą jej wartość. W powyższym przykładzie po utworzeniu kontrolki przez funkcję CreateWindowEx, zapisujemy jej uchwyt (zwrócony właśnie przez funckję CreateWindowEx) do zmiennej hEdit. Nie jest to konieczne, ale wartość ta może się jeszcze przydać w programie.
Aby utworzyć przyciski, postępujemy według tego samego schematu:
invoke CreateWindowEx,0,ADDR classButton,ADDR Button2Text,\ WS_CHILD or WS_VISIBLE,75,70,140,25,hWnd,Button2ID,hInstance,0 mov hButton2,eax
invoke CreateWindowEx,0,ADDR classButton,ADDR Button3Text,\
WS_CHILD or WS_VISIBLE,75,100,140,25,hWnd,Button3ID,hInstance,0
mov hButton3,eax
invoke CreateWindowEx,0,ADDR classButton,ADDR Button4Text,\
WS_CHILD or WS_VISIBLE,75,130,140,25,hWnd,Button4ID,hInstance,0
mov hButton4,eax
.data
classButton db „BUTTON”,0
Button2Text db „Odczytaj”,0
Button3Text db „Wpisz”,0
Button4Text db „Wyjdź”,0
.data?
hButton2 dd ?
hButton3 dd ?
hButton4 dd ?
.const
Button2ID equ 3
Button3ID equ 4
Button4ID equ 5
Mamy więc okno, a w nim pole tekstowe i trzy przyciski. W jaki sposób teraz zareagować na kliknięcie jednego z nich? Robimy to w zdarzeniu WM_COMMAND, które jest wysyłane za każdym razem gdy zostanie wybrana pozycja w menu lub kliknięta kontrolka oraz w kilku innych przypadkach. Na dobry początek obsłużmy przycisk z napisem „Wyjście” (Button4ID).
.ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==Button4ID invoke PostQuitMessage,0
Numer (ID) kontrolki, która wygenerowała zdarzenie jest zapisany w niższym słowie zmiennej wParam. Dlatego właśnie umieszczamy tą zmienną w rejestrze eax – dzięki temu mamy bezpośredni dostęp do niższego słowa, gdyż jak wiemy z lekcji pierwszej, niższym słowem rejestru eax jest rejestr ax. Następnie sprawdzamy czy rejestr ten ma wartość Button4ID. Jeżeli tak, zamykamy okno i wyłączamy program za pomocą funckji PostQuitMessage.
Przycisk Button2ID ma za zadanie pobrać tekst z pola tekstowego i wyświetlić go w okienku z komunikatem (patrz: lekcja 2). Do pobierania tekstu slużą dwie różne funckje API: GetWindowText (stosuje sie ja gdy znamy handle kontrolki) oraz GetDlgItemText (stosuje sie gdy znamy ID kontrolki oraz handle jej okna macierzystego, nie musimy znać uchwytu kontrolki). Ponieważ uchwyt pola tekstowego zapisalismy w zmiennej hEdit zaraz po jego utworzeniu, zastosujemy funckję GetWindowText. Oto parametry, których potrzebuje:
- hWnd – uchwyt okna (kontrolki) z ktorego chcemy pobrać tekst
- lpString – adres bufora na tekst
- nMaxCount – maksymalna ilość znaków do pobrania, najczęściej podaje się tutaj po prostu wielkość bufora
Oto parametry funckji GetDlgItemText:
- hDlg – uchwyt okna macierzystego
- nIDDlgItem – ID kontrolki z ktorej chcemy pobrać tekst
- lpString – adres bufora na tekst
- nMaxCount – maksymalna ilość znaków do pobrania, najczęściej podaje się tutaj po prostu wielkość bufora
Kod obsługujący przycisk Button2ID jest następujący:
.ELSEIF ax==Button2ID invoke GetWindowText,hEdit,offset BuforNaText,sizeof BuforNaText-1 invoke MessageBox,hWnd,addr BuforNaText,addr Tytul,MB_ICONEXCLAMATION
W drugiej linii pobieramy tekst z kontrolki hEdit do bufora BuforNaText. Operator „sizeof” zwraca dlugość danej zmiennej, czyli w tym przypadku BuforNaText. Od tej wartości odejmujemy jeden ponieważ, jak pamiętamy, każda zmienna tekstowa musi kończyć się zerem. Funkcja GetWindowText wymaga podania maksymalnej ilości znaków do pobrania, nie licząc tego właśnie zera. Jednak na zero również musi być miejsce w buforze. Dlatego gdy mamy bufor 100-bajtowy, możemy w nim umieścić tekst składający się najwyżej z 99 znaków oraz końcowego zera. Zero jest dopisywane automatycznie przez funkcję GetWindowText. Zadeklarujmy jeszcze zmienną BuforNaText:
.data? BuforNaText db 100 dup(?)
Przycisk 3 służy do umieszczania tekstu w polu tekstowym. Podobnie jak poprzenio, programiści z Microsoftu przewidzieli do tego celu dwie funckje: SetWindowText:
- hWnd – uchwyt okna (kontrolki) w którym umieszczamy tekst
- lpString – adres bufora z tekstem
oraz SetDlgItemText:
- hDlg – uchwyt okna macierzystego
- nIDDlgItem – ID kontrolki w której chcemy umieścić tekst
- lpString – adres bufora z tekstem
Oto kod dla przycisku Button3ID, który umieszcza w polu tekstowym napis „Przykładowy text”:
.ELSEIF ax==Button3ID invoke SetWindowText,hEdit,offset TekstPrzykladowy .data TekstPrzykladowy db "Przykladowy text",0
Cały kod programu, który dzisiaj napisaliśmy (wraz z wersja skompilowaną) można pobrać tutaj.
Czy jest jakaś funkcja w masm dzięki której mogę wywołać opóźnienie czasowe????
Dzieki za ciekawe informacje
@PPP:
Jest taka funkcja, nazywa się Sleep();.