![]() |
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: DWORDPozostał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