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