Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr
Jerzy Wałaszek |
|
Na tej lekcji nauczymy się o przejmowaniu procedury okna (window subclassing), co to jest oraz jak zastosować to dla naszych własnych korzyści.
Kod źródłowy możesz pobrać {z tego archiwum}.
Jeśli programujesz w systemie Windows już od pewnego czasu, to na pewno napotkałeś sytuację, gdzie jakieś okno posiadało prawie wszystkie potrzebne ci własności, lecz nie do końca. Czy była ci kiedykolwiek potrzebna specjalna kontrolka edycyjna, która filtruje niektóre niepożądane informacje? Bezpośrednim podejściem byłoby zaprojektowanie własnego okna. Ale to na prawdę ciężka i zajmująca czas praca. Rozwiązaniem jest przejęcie procedury okna.
Krótko mówiąc, przejęcie procedury okna pozwala ci "przejąć" to okno. Będziesz miał absolutną kontrolę nad nim. Zobaczmy na prosty przykład. Załóżmy, iż potrzebujesz pola tekstowego, które akceptuje jedynie cyfry szesnastkowe. Jeśli zastosujesz proste pole edycji, to nie będziesz miał nic do powiedzenia, gdy twój użytkownik wpisze coś innego od cyfr szesnastkowych w to pole edycji, tj. jeśli wpisze on "zb+g", to nic nie możesz zrobić, a jedynie odrzucić cały tekst. Mało profesjonalne rozwiązanie. W istocie potrzebujesz możliwości zbadania wprowadzanych przez użytkownika znaków do pola edycyjnego w momencie naciskania przez niego klawiszy.
Teraz zbadajmy, jak tego dokonać. Gdy użytkownik wpisuje cokolwiek do pola edycyjnego, system Windows wysyła wiadomość WM_CHAR do procedury okna tej kontrolki. Ta procedura okna przebywa wewnątrz samego Windows, zatem nie możemy jej zmienić. Jednakże możemy dokonać przekierowania przepływu wiadomości do naszej własnej procedury okna. W ten sposób nasza procedura okna będzie z pierwszej ręki otrzymywała wszystkie wiadomości wysyłane przez system Windows do kontrolki edycyjnej. Jeśli zdecyduje się ona podjąć jakieś działania przy danej wiadomości, to może to bez problemów robić. Lecz jeśli nie chce obsługiwać danej wiadomości, to może przekazać ją do właściwej procedury okna. I tak nasza procedura okna wstawia się pomiędzy Windows a kontrolkę edycji. Spójrz na poniższy schemat:
Przed przejęciem procedury okna
System Windows → procedura okna kontrolki edycji
Po przejęciu procedury okna
System Windows ® nasza procedura okna ® procedura okna kontrolki edycji
Teraz skoncentrujemy się nad sposobem przejmowania procedury okna. Zapamiętaj, iż nie jest to ograniczone do kontrolek, może być zastosowane z każdym oknem.
Zastanówmy się, skąd system Windows wie, gdzie znajduje się procedura okna kontrolki edycyjnej. Jakieś pomysły?... Pole lpfnWndProc struktury WNDCLASSEX. Jeśli moglibyśmy podmienić zawartość tego pola z adresem naszej własnej procedury okna, to system Windows wysyłałby wiadomości do nas, a nie do kontrolki edycyjnej.
Możemy to zrobić wywołując funkcję SetWindowLong.
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
Zatem mamy proste zadanie: tworzymy procedurę okna, która obsłuży wiadomości dla kontrolki edycji, a następnie wywołujemy funkcję SetWindowLong ze znacznikiem GWL_WNDPROC przekazując w trzecim parametrze adres naszej procedury okna. Jeśli funkcja się powiedzie, to wartością zwrotną jest 32-bitowa zawartość zamienionego pola struktury WNDCLASSEX, w naszym przypadku jest to adres oryginalnej procedury okna kontrolki. Musimy zapamiętać tę wartość do użytku z naszą procedurą okna.
Pamiętaj, iż pojawi się kilka wiadomości, których nie chcemy obsługiwać i przekażemy je do oryginalnej procedury okna. Możemy tego dokonać wywołując funkcję CallWindowProc:
CallWindowProc PROTO lpPrevWndFunc: DWORD, \ hWnd: DWORD,\ Msg: DWORD,\ wParam: DWORD,\ lParam: DWORD
Pozostałe cztery parametry są parametrami przekazanymi do naszej procedury okna. Po prostu przesyłamy je dalej do CallWindowProc.
Plik SUBCLASS.ASM
.386 .MODEL FLAT, STDCALL OPTION CASEMAP:NONE INCLUDE \masm32\include\windows.inc INCLUDE \masm32\include\user32.inc INCLUDE \masm32\include\kernel32.inc INCLUDE \masm32\include\comctl32.inc INCLUDELIB \masm32\lib\comctl32.lib INCLUDELIB \masm32\lib\user32.lib INCLUDELIB \masm32\lib\kernel32.lib WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD EditWndProc PROTO :DWORD, :DWORD, :DWORD, :DWORD .DATA ClassName DB "SubclassWinClass", 0 AppName DB "Przejmowanie procedury okna", 0 EditClass DB "EDIT", 0 Message DB "W oknie edycyjnym nacisnąłeś klawisz Enter!", 0 .DATA? hInstance HINSTANCE ? hwndEdit DD ? 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,\ ADDR ClassName, ADDR AppName,\ WS_OVERLAPPED + WS_CAPTION + \ WS_SYSMENU + WS_MINIMIZEBOX + \ WS_MAXIMIZEBOX + WS_VISIBLE, CW_USEDEFAULT,\ CW_USEDEFAULT, 350, 100, 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 hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_CREATE INVOKE CreateWindowEx, WS_EX_CLIENTEDGE,\ ADDR EditClass, NULL,\ WS_CHILD + WS_VISIBLE + WS_BORDER,\ 20, 20, 300, 25, hWnd, NULL,\ hInstance, NULL mov hwndEdit, eax INVOKE SetFocus, eax ;----------------------------------------- ; Przejmujemy procedurę okna ;----------------------------------------- INVOKE SetWindowLong, hwndEdit, GWL_WNDPROC, ADDR EditWndProc mov OldWndProc, eax .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 .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
Za pomocą EditWndProc filtrujemy wiadomości WM_CHAR. Jeśli znak jest pomiędzy 0...9 lub a...f, akceptujemy go przekazując wiadomość oryginalnej procedurze okna. Jeśli znak jest małą literą, zamieniamy go na dużą literę dodając do jego kodu wartość szesnastkową 20h. Zwróć uwagę, iż jeżeli znak nie jest taki, jak oczekujemy, to ignorujemy go. Nie przekazujemy go do oryginalnej procedury okna. Zatem gdy użytkownik wpisze coś innego niż 0...9 lub a...f, to znak ten po prostu nie pojawi się w kontrolce edycyjnej.
.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 .ELSE INVOKE CallWindowProc, OldWndProc, hEdit, uMsg, wParam, lParam ret .ENDIF
Chcę dalej zademonstrować potęgę przejmowania procedury okna przy
przechwytywaniu klawisza Enter. EditWndProc sprawdza
wiadomość
WM_KEYDOWN na klawisz VK_RETURN
Możesz wykorzystać przejmowanie procedury okna do przejęcia kontroli nad innymi oknami. Jest to jedna z potężnych technik, które powinny się znaleźć w twoim arsenale programisty.
Ta sama aplikacja w Pascalu:
{******************************** ** I Liceum Ogólnokształcące ** ** w Tarnowie ** ** mgr Jerzy Wałaszek ** ********************************} program SubClassing; uses Windows; const ClassName = 'SubclassWinClass'; AppName = 'Przejmowanie procedury okna'; EditClass = 'EDIT'; Message = 'W oknie edycyjnym nacisnąłeś klawisz Enter!'; type FOldWndProc = function (a:longint;b:longword;c:longword;d:longint):longint; var hInstance : HINST; hwndEdit : longword; OldWndProc : longword; function EditWndProc(hEdit:DWORD;uMsg:DWORD;wParam:DWORD;lParam:DWORD):DWORD; var c : char; 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 CallWindowProc(FOldWndProc(OldWndProc),hEdit,uMsg,ord(c),lParam); end; WM_KEYDOWN: if (wParam and $ff) = VK_RETURN then begin MessageBox(hEdit,Message,AppName,MB_OK + MB_ICONINFORMATION); SetFocus(hEdit); end else Result := CallWindowProc(FOldWndProc(OldWndProc),hEdit,uMsg,wParam,lParam); else Result := CallWindowProc(FOldWndProc(OldWndProc),hEdit,uMsg,wParam,lParam); end; end; function WndProc(hWnd:HANDLE;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint; begin Result := 0; case uMsg of WM_CREATE: begin hwndEdit := CreateWindowEx(WS_EX_CLIENTEDGE,EditClass,0, WS_CHILD + WS_VISIBLE + WS_BORDER,20,20,300,25, hWnd,0,hInstance,0); SetFocus(hwndEdit); //----------------------------------------- // Przejmujemy procedurę okna //----------------------------------------- OldWndProc := SetWindowLong(hwndEdit,GWL_WNDPROC,longint(@EditWndProc)); 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,ClassName,AppName, WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_MINIMIZEBOX + WS_MAXIMIZEBOX + WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT, 350,100,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