Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr
Jerzy Wałaszek |
|
Na tej lekcji zapoznamy się z przejmowaniem klasy okna (window superclassing). Dowiemy się co to jest i do czego ono służy. Również nauczysz się dodawania nawigacji klawiszem TAB do kontrolek w swoim własnym oknie.
Kod źródłowy możesz pobrać {z tego archiwum}.
W swojej karierze programisty z pewnością spotkasz sytuacje, gdy musisz umieścić w oknie kilka kontrolek o "nieco" innym działaniu niż standardowe. Na przykład możesz potrzebować 10 kontrolek edycyjnych, które akceptują jedynie liczby. Istnieje kilka sposobów osiągnięcia tego celu:
Pierwsza metoda jest zbyt męcząca. Musiałbyś samemu zaimplementować całe działanie kontrolki edycyjnej. Trudne zadanie do podjęcia się go. Druga metoda jest już lepsza od pierwszej, lecz wciąż wymaga dużo pracy. Można jej użyć przy kilku kontrolkach, lecz zmienia się w koszmar nocny, jeśli musimy przejąć procedury okna tuzina lub więcej kontrolek. W takiej sytuacji najbardziej odpowiednią techniką jest przejęcie klasy okna (superclassing).
Przejęcie klasy okna jest metodą używaną do "przejęcia kontroli" nad określoną klasą okna. Przez "przejęcie kontroli" mam na myśli, iż możesz zmodyfikować własności klasy okna, aby przystosować ją do własnych celów, a następnie utworzenie na jej podstawie wiązanki kontrolek.
Poniżej wymieniłem kroki przy przejmowaniu klasy okna:
Przejęcie klasy okna (superclassing) jest lepsze od przejmowania procedury okna (subclassing), jeśli planujesz utworzenie wielu kontrolek o tych samych charakterystykach.
Plik SUPERCLASS.ASM
.386 .MODEL FLAT, STDCALL OPTION CASEMAP:NONE INCLUDE \masm32\include\windows.inc INCLUDE \masm32\include\user32.inc INCLUDE \masm32\include\kernel32.inc INCLUDELIB \masm32\lib\user32.lib INCLUDELIB \masm32\lib\kernel32.lib WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD EditWndProc PROTO :DWORD, :DWORD, :DWORD, :DWORD .CONST WM_SUPERCLASS EQU WM_USER+5 .DATA ClassName DB "SuperclassWinClass", 0 AppName DB "Przejmowanie klasy okna", 0 EditClass DB "EDIT", 0 OurClass DB "SUPEREDITCLASS", 0 Message DB "Nacisnąłeś klawisz Enter w polu tekstowym!", 0 .DATA? hInstance DD ? hwndEdit DD 6 DUP(?) OldWndProc DD ? .CODE start: INVOKE GetModuleHandle, NULL mov hInstance, eax INVOKE WinMain, hInstance, NULL, NULL, SW_SHOWDEFAULT INVOKE ExitProcess, eax WinMain PROC hInst: HINSTANCE,\ hPrevInst: HINSTANCE,\ CmdLine: LPSTR,\ CmdShow: DWORD LOCAL wc: WNDCLASSEX LOCAL msg: MSG LOCAL hwnd: HWND 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_APPWORKSPACE mov wc.lpszMenuName, NULL 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, WS_EX_CLIENTEDGE + WS_EX_CONTROLPARENT,\ ADDR ClassName, ADDR AppName,\ WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + \ WS_MINIMIZEBOX + WS_MAXIMIZEBOX + WS_VISIBLE,\ CW_USEDEFAULT, CW_USEDEFAULT, 350, 220, NULL, NULL,\ hInst, NULL mov hwnd, eax .WHILE TRUE INVOKE GetMessage, ADDR msg, NULL, 0, 0 .BREAK .IF (!eax) INVOKE TranslateMessage, ADDR msg INVOKE DispatchMessage, ADDR msg .ENDW mov eax, msg.wParam ret WinMain ENDP WndProc PROC USES ebx edi hWnd: HWND,\ uMsg: UINT,\ wParam: WPARAM,\ lParam: LPARAM LOCAL wc: WNDCLASSEX .IF uMsg==WM_CREATE mov wc.cbSize, SIZEOF WNDCLASSEX INVOKE GetClassInfoEx, NULL, ADDR EditClass, ADDR wc push wc.lpfnWndProc pop OldWndProc mov wc.lpfnWndProc, OFFSET EditWndProc push hInstance pop wc.hInstance mov wc.lpszClassName, OFFSET OurClass INVOKE RegisterClassEx, ADDR wc xor ebx, ebx mov edi, 20 .WHILE ebx<6 INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\ ADDR OurClass, NULL,\ WS_CHILD + WS_VISIBLE + WS_BORDER, 20,\ edi, 300, 25, hWnd, ebx,\ hInstance, NULL mov DWORD PTR [hwndEdit+4*ebx], eax add edi, 25 inc ebx .ENDW INVOKE SetFocus, hwndEdit .ELSEIF uMsg==WM_DESTROY INVOKE PostQuitMessage, NULL .ELSE INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam ret .ENDIF xor eax, eax ret WndProc ENDP EditWndProc PROC hEdit:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD .IF uMsg==WM_CHAR mov eax, wParam .IF (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK .IF al>="a" && al<="f" sub al, 20h .ENDIF INVOKE CallWindowProc, OldWndProc, hEdit, uMsg, eax, lParam ret .ENDIF .ELSEIF uMsg==WM_KEYDOWN mov eax, wParam .IF al==VK_RETURN INVOKE MessageBox, hEdit,\ ADDR Message, ADDR AppName,\ MB_OK + MB_ICONINFORMATION INVOKE SetFocus, hEdit .ELSEIF al==VK_TAB INVOKE GetKeyState, VK_SHIFT test eax, 80000000H .IF ZERO? INVOKE GetWindow, hEdit, GW_HWNDNEXT .IF eax==NULL INVOKE GetWindow, hEdit, GW_HWNDFIRST .ENDIF .ELSE INVOKE GetWindow, hEdit, GW_HWNDPREV .IF eax==NULL INVOKE GetWindow, hEdit, GW_HWNDLAST .ENDIF .ENDIF INVOKE SetFocus, eax xor eax, eax ret .ELSE INVOKE CallWindowProc, OldWndProc, hEdit, uMsg, wParam, lParam ret .ENDIF .ELSE INVOKE CallWindowProc, OldWndProc, hEdit, uMsg, wParam, lParam ret .ENDIF xor eax, eax ret EditWndProc ENDP END start
Program utworzy proste okno z 6 "zmodyfikowanymi" kontrolkami edycji w jego obszarze roboczym. Kontrolki edycyjne będą akceptowały tylko cyfry szesnastkowe. Właściwie to zmodyfikowałem przykład z przejmowaniem procedury okna, aby zastosować go w przejmowaniu klasy, stąd duże podobieństwo kodu. Program uruchamia się normalnie, a interesujący fragment występuje w sekcji tworzenia okna:
.IF uMsg==WM_CREATE mov wc.cbSize, SIZEOF WNDCLASSEX INVOKE GetClassInfoEx, NULL, ADDR EditClass, ADDR wc
Najpierw musimy wypełnić strukturę WNDCLASSEX danymi z klasy, którą chcemy przejąć, czyli klasy EDIT. Pamiętaj o ustawieniu pola cbSize w strukturze WNDCLASSEX przed wywołaniem GetClassInfoEx, w przeciwnym razie struktura WNDCLASSEX nie zostanie wypełniona prawidłowo. Po powrocie z GetClassInfoEx struktura wc wypełniona jest całą potrzebną nam informacją do utworzenia nowej klasy okna.
push wc.lpfnWndProc pop OldWndProc mov wc.lpfnWndProc, OFFSET EditWndProc push hInstance pop wc.hInstance mov wc.lpszClassName, OFFSET OurClass
Teraz musimy zmodyfikować kilka pól struktury wc. Pierwszym jest wskazanie do procedury okna. Ponieważ będziemy musieli połączyć naszą własną procedurę okna z oryginalna procedurą, niezbędne jest zapamiętanie zawartości tego pola w zmiennej OldWndProc, aby później nie było problemów z wywołaniem za pomocą funkcji CallWindowProc. Ta technika jest identyczna jak w przejmowaniu procedury okna, z wyjątkiem tego, iż modyfikuje się strukturę WNDCLASSEX bezpośrednio a nie za pomocą wywołania SetWindowLong. Następne dwa pola, hInstance oraz lpsClassName, muszą być zmienione, w przeciwnym razie system nie zarejestruje twojej nowej klasy okna. Musisz wymienić oryginalną zawartość pola hInstance z wartością hInstance twojego własnego programu. Również należy wybrać nową nazwę dla nowej klasy.
INVOKE RegisterClassEx, ADDR wc
Gdy wszystko będzie gotowe, rejestrujemy nową klasę. Będzie ona posiadała niektóre własności starej klasy.
xor ebx, ebx mov edi, 20 .WHILE ebx<6 INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\ ADDR OurClass, NULL,\ WS_CHILD + WS_VISIBLE + WS_BORDER, 20,\ edi, 300, 25, hWnd, ebx,\ hInstance, NULL mov DWORD PTR [hwndEdit+4*ebx], eax add edi, 25 inc ebx .ENDW INVOKE SetFocus, hwndEdit
Po zarejestrowaniu tej klasy można na jej podstawie tworzyć okna. W powyższym fragmencie kodu używam rejestru ebx jako licznika ilości utworzonych okien. Rejestr edi używany jest jako współrzędna y lewego górnego narożnika okna. Gdy okno jest tworzone, jego uchwyt zostaje umieszczony w tablicy podwójnych słów (DWORD). Po utworzeniu wszystkich okien ustawiamy skupienie na pierwszym oknie.
W tym momencie otrzymujemy 6 kontrolek edycyjnych, które akceptują tylko cyfry szesnastkowe. Zastąpiona procedura okna obsługuje ten filtr. Właściwie jest ona identyczna z procedurą okna w przykładzie przejmowania procedury okna. Jak widzisz, nie musisz wykonywać dodatkowej pracy.
Dorzuciłem fragment kodu obsługujący nawigację klawiszem TAB, aby nadać temu przykładowi więcej treści. Zwykle, gdy umieszczasz kontrolki w oknie dialogowym, menadżer okna dialogowego obsługuje dla ciebie klawisze nawigacyjne, zatem możesz przemieszczać się do następnej kontrolki klawiszem TAB, a do poprzedniej za pomocą SHIFT-TAB. Niestety taka usługa nie jest dla ciebie dostępna, jeśli umieścisz swoje kontrolki w prostym oknie. Musisz przejąć ich procedury okien, aby umożliwić obsługę klawisza TAB. W naszym przykładzie nie musimy przejmować procedur okien kontrolek jednej za drugą, ponieważ już przejęliśmy ich klasę, zatem możemy utworzyć dla nich "centralnego zarządcę nawigacji".
.ELSEIF al==VK_TAB INVOKE GetKeyState, VK_SHIFT test eax, 80000000 .IF ZERO? INVOKE GetWindow, hEdit, GW_HWNDNEXT .IF eax==NULL INVOKE GetWindow, hEdit, GW_HWNDFIRST .ENDIF .ELSE INVOKE GetWindow, hEdit, GW_HWNDPREV .IF eax==NULL INVOKE GetWindow, hEdit, GW_HWNDLAST .ENDIF .ENDIF INVOKE SetFocus, eax xor eax, eax ret
Powyższy fragment kodu pochodzi z procedury EditWndClass. Sprawdza on, czy użytkownik nacisnął klawisz TAB, a jeśli tak, to wywołuje funkcję GetKeyState w celu sprawdzenia, czy jest naciśnięty również klawisz SHIFT. Funkcja GetKeyState zwraca wartość w rejestrze eax, która określa, czy zadany klawisz jest naciśnięty, czy nie. Jeśli klawisz jest naciśnięty, to najstarszy bit rejestru eax zostaje ustawiony na 1. Jeśli nie, najstarszy bit ma wartość 0. Zatem testujemy najstarszy bit eax (test bitowy z 80000000h). Ustawienie najstarszego bitu na 1 oznacza, iż użytkownik nacisnął kombinację klawiszy SHIFT+TAB, co musimy obsłużyć oddzielnie.
Jeśli użytkownik naciśnie sam klawisz TAB, wywołujemy GetWindow, aby pobrać uchwyt następnej kontrolki. Stosujemy znacznik GW_HWNDNEXT, aby poinformować funkcję GetWindow, iż powinna pobrać uchwyt do okna następnego po bieżącym hEdit. Jeśli funkcja ta zwróci NULL, to potraktujemy to jako koniec uchwytów do pobrania, zatem obecny uchwyt hEdit wskazuje ostatnią kontrolkę w szeregu. Przewiniemy skupienie do pierwszej kontrolki wywołując GetWindow ze znacznikiem GW_HWNDFIRST. W podobny sposób obsługujemy kombinację klawiszy SHIFT-TAB pamiętając, iż powinna ona dawać efekt odwrotny.
Ta sama aplikacja w Pascalu:
{******************************** ** I Liceum Ogólnokształcące ** ** w Tarnowie ** ** mgr Jerzy Wałaszek ** ********************************} program Superclass; uses Windows; const WM_SUPERCLASS = WM_USER + 5; ClassName = 'SuperclassWinClass'; AppName = 'Przejmowanie klasy okna'; EditClass = 'EDIT'; OurClass = 'SUPEREDITCLASS'; Messagetx = 'Nacisnąłeś klawisz Enter w polu tekstowym!'; type TOldWndProc = function (a,b,c,d:longword) : longint; var hInstance : HINST; hwndEdit : array[0..5] of HANDLE; OldWndProc : TOldWndProc; function EditWndProc(hEdit,uMsg,wParam,lParam:longword) : longint; var c : char; h : HANDLE; begin Result := 0; case uMsg of WM_CHAR: begin c := UpCase(char(wParam and $ff)); if (c in ['0'..'9']) or (c in ['A'..'F']) or (ord(c) = VK_BACK) then Result := CallWindowProc(OldWndProc,hEdit,uMsg,ord(c),lParam); end; WM_KEYDOWN: begin case (wParam and $ff) of VK_RETURN: begin MessageBox(hEdit,Messagetx,AppName,MB_OK + MB_ICONINFORMATION); SetFocus(hEdit); end; VK_TAB: begin if (GetKeyState(VK_SHIFT) and $80000000) = 0 then begin h := GetWindow(hEdit,GW_HWNDNEXT); if h = 0 then h := GetWindow(hEdit,GW_HWNDFIRST); end else begin h := GetWindow(hEdit,GW_HWNDPREV); if h = 0 then h := GetWindow(hEdit,GW_HWNDLAST); end; SetFocus(h); end; else Result := CallWindowProc(OldWndProc,hEdit,uMsg,wParam,lParam); end; end; else Result := CallWindowProc(OldWndProc,hEdit,uMsg,wParam,lParam); end; end; function WndProc(hWnd:HWND;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint; var wc : WNDCLASSEX; i,y : integer; begin Result := 0; case uMsg of WM_CREATE: begin wc.cbSize := sizeof(WNDCLASSEX); GetClassInfoEx(0,EditClass,wc); OldWndProc := wc.lpfnWndProc; wc.lpfnWndProc := @EditWndProc; wc.hInstance := hInstance; wc.lpszClassName := OurClass; RegisterClassEx(wc); y := 20; for i := 0 to 5 do begin hwndEdit[i] := CreateWindowEx(WS_EX_CLIENTEDGE,OurClass,0, WS_CHILD + WS_VISIBLE + WS_BORDER,20,y,300,25, hWnd,i,hInstance,0); y := y + 25; end; SetFocus(hwndEdit[0]); end; WM_DESTROY: PostQuitMessage(0); else Result := DefWindowProc(hWnd,uMsg,wParam,lParam); end; end; function WinMain(hInst,hPrevInst:HINST;CmdLine:LPSTR;CmdShow:DWORD): longint; var wc : WNDCLASSEX; msg : MSG; hwnd : HWND; begin with wc do begin cbSize := sizeof(WNDCLASSEX); style := CS_HREDRAW or CS_VREDRAW; lpfnWndProc := @WndProc; cbClsExtra := 0; cbWndExtra := 0; hInstance := hInst; hbrBackground := COLOR_APPWORKSPACE; lpszMenuName := 0; lpszClassName := ClassName; hIcon := LoadIcon(0,IDI_APPLICATION); hIconSm := hIcon; hCursor := LoadCursor(0,IDC_ARROW); end; RegisterClassEx(wc); hwnd := CreateWindowEx(WS_EX_CLIENTEDGE + WS_EX_CONTROLPARENT, ClassName,AppName,WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_MINIMIZEBOX + WS_MAXIMIZEBOX + WS_VISIBLE, CW_USEDEFAULT,CW_USEDEFAULT,350,220,0,0,hInst,0); while GetMessage(msg,0,0,0) do begin TranslateMessage(msg); DispatchMessage(msg); end; Result := msg.wParam; end; begin hInstance := GetModuleHandle(0); ExitProcess(WinMain(hInstance,0,0,SW_SHOWDEFAULT)); end.
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