Tutorial 22: Fenster ableiten (Superclassing)
In diesem Tutorial werden wir was über Fenster ableiten (Superclassing) lernen, was es ist und wofür es gut ist. Sie werden auch lernen, wie man mit der Tab Taste durch die Steuerelemente in Ihren eigenen Fenstern navigiert.
Laden Sie das Beispiel hier herunter.
Theorie:
In Ihrer Programmier Karriere werden Sie sicherlich der Situation begegnen, dass Sie verschiedene Steuerelemente benötigen, die ein *leicht* verschiedenes Verhalten haben. Zum Beispiel, könnten Sie 10 Edit Steuerelemente gebrauchen, die nur Zahlen akzeptieren. Es gibt verschiedene Wege, dieses Ziel zu erreichen:
-
Erzeugen Sie Ihre eigene Klasse und instantiieren Sie die Steuerelemente
-
Erzeugen Sie diese Edit Steuerelemente und benutzen Sie die Subclass Technik.
-
Leiten Sie die Edit Steuerelemente mit der Superclass Technik ab.
Die erste Methode ist zu ermüdend. Sie müssen jede Funktionalität des Edit Steuerelements selbst implementieren. Keine leichte Aufgabe. Die zweite Methode ist besser als die erste, aber immer noch zuviel Arbeit. Es ist für einige wenige Steuerelemente in Ordnung, aber es wird ein Albtraum wenn Sie ein Dutzend Steuerelemente oder so haben. Superclassing ist die Technik, die Sie für solche Vorhaben benutzen sollten.
Subclassing ist die Methode, die Sie benutzen, wenn Sie die *Kontrolle* über eine einzelne Fenster-Klasse übernehmen wollen, ich meine, Sie können die Eigenschaften der Fenster-Klasse modifizieren um sie Ihren Vorhaben anzupassen und dann die Steuerelemente erzeugen.
Die Schritte für Superclassing werden folgend beschrieben:
-
rufen Sie GetClassInfoEx
auf, um die Informationen über die Fenster-Klasse zu erhalten, die Sie ableiten wollen.
GetClassInfoEx
benötigt einen Zeiger auf eine WNDCLASSEX Struktur welche mit den nötigen Informationen gefüllt wird, wenn der Aufruf erfolgreich zurückkehrt.
-
Modifizieren Sie die gewünschten WNDCLASSEX Elemente. Es gibt zwei Elemente, die Sie ändern MÜSSEN:
-
hInstance
Sie müssen in diesem Element das Handle Ihres Programms eintragen.
-
lpszClassName
Sie müssen einen Zeiger auf den neuen Klassennamen angeben.
Das lpfnWndProc Element müssen Sie nicht ändern aber in den meisten Fällen, müssen Sie es tun. Vergessen Sie nicht, das original lpfnWndProc Element zu speichern, wenn Sie es mit CallWindowProc aufrufen wollen.
-
Registrieren Sie die modifizierte WNDCLASSEX Struktur. Sie werden eine neue Fenster-Klasse mit verschiedene Charakteristika der alten Fenster-Klasse haben.
-
Erzeugen Sie Fenster mit der neuen Fenster-Klasse.
Superclassing ist besser als Subclassing wenn Sie viele Steuerelemente mit den selben Charakteristika erstellen wollen.
Beispiel:
.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
WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "Sie haben die Enter Taste in der Text Box gedrückt!",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="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,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
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndP
Analyse:
Das Programm erzeugt ein einfaches Fenster mit 6 "modifizierten" Edit Steuerelemente in seinen Client Area. Die Edit Steuerelemente akzeptieren nur Hexadezimale Ziffern. Eigentlich habe ich nur das Subclassing Beispiel modifiziert, dass jetzt Superclassing ausgeführt wird. Das Programm startet normal und der interssante Teil wird ausgeführt, wenn das Haupt-Fenster erzeugt wird:
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
Wir müssen die WNDCLASSEX Struktur mit den Daten aus der Klasse, die wir ableiten möchten, füllen, in diesem Fall ist es die EDIT Klasse. Erinnern Sie sich, dass Sie das cbSize Element der WNDCLASSEX Struktur setzen müssen, bevor Sie GetClassInfoEx aufrufen, ansonsten ist die WNDCLASSEX Struktur nicht richtig gefüllt. Nachdem GetClassInfoEx zurückkehrt, ist wc mit all den Informationen gefüllt, die wir benötigen, die neue Fenster-Klasse zu erzeugen.
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
Nun müssen wir einige Elemente von wc verändern. Das erste ist der Zeiger auf die Fenster-Prozedur. Da wir unsere eigene Fenster-Prozedur mit der originalen verketten müssen, müssen wir sie in einer Variablen speichern, so dass wir CallWindowProc benutzen können. Diese Technik ist identisch mit dem Subclassing, ausser, dass Sie die WNDCLASSEX Struktur direkt ändern ohne SetWindowLong aufrufen zu müssen. Die nächsten beiden Elemente müssen geändert werden, ansonsten werden Sie nicht in der Lage sein, Ihre neue Fenster-Klasse zu registrieren,
hInstance and lpsClassName. Sie müssen den original hInstance Wert mit hInstance Ihres Programms ersetzen. Und Sie müssen einen neuen Namen für die neue Klasse wählen.
invoke RegisterClassEx, addr wc
Wenn alles getan ist, registrieren Sie die neue Klasse. Sie werden eine neue Klasse erhalten mit den Eigenschaften einer alten Klasse.
xor ebx,ebx
mov edi,20
.while ebx
Nun wo wir die Klasse registriert haben, können wir auf ihr basierend Fenster erzeugen. In dem obigen Ausschnitt benutzen ich EBX als Zähler für die Anzahl erzeugter Fenster. EDI wird als y Koordinate der linken oberen Ecke des Fensters benutzt. Wenn ein Fenster erzeugt ist, wird sein Handle im DWord Array gespeichert. Wenn alle Fenster erzeugt wurden, wird der Eingabefokus auf das erste Fenster gesetzt.
Zu diesem Zeitpunkt haben Sie 6 Edit Steuerlemente, die nur Hex-Zahlen akzeptieren. Tatsächlich ist es mit der Fenster-Prozedur aus dem Subclassing Beispiel identisch. Wie Sie sehen können, müssen Sie keine extra Arbeit wie beim Subclassing leisten.
Ich werfe ein Code Ausschnitt ein, um die Tab-Navigation zu händeln, um dieses Tutorial interessanter zu machen. Normalerweise händelt der Dialog Box Manager die Navigations Tasten, wenn Sie Steuerelemente in einer Dialog Box plazieren, so dass Sie Tab benutzen können, um zum nächsten Steuerlemente zu gelangen, oder Shift-Tab um zum vorherigen zu kommen. Ein solches Feature ist nicht verfügbar, wenn Sie die Steuerelemente in einem einfachen Fenster plazieren. Sie müssen Subclassing benutzen, so dass Sie die Tab-Tasten selbst händeln können. In unserem Beispiel müssen wir die Steuerelemente nicht 'subclassen', da wir sie schon 'superclassed' haben, weswegen wir einen "zentralen Steuerelemente Navigations Manger" für diese bereitstellen.
.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
Der obige Code Ausschnitt ist aus der EditWndClass Prozedur. Sie überprüft, ob der Benutzer die Tab Taste gedrückt hat, und wenn dem so ist, wird GetKeyState aufgerufen, um zu überprüfen, ob die SHIFT Taste auch gedrückt wurde. GetKeyState gibt einen Wert in EAX zurück, der angibt, ob die spezifierte Taste gedrückt wurde oder nicht. Wenn die Taste gedrückt wurde, wird das oberste Bit von EAX gesetzt. Wenn nicht, wird das oberste Bit gelöscht. So können wir den Rückgabewert einfach mit 80000000h testen. Wenn das oberste Bit gesetzt ist, bedeutet das, dass der Benutzer Shift+Tab gedrückt hat, was wir seperat behandeln müssen.
Wenn der Benutzer die Tab Taste allein gedrückt hat, rufen wir GetWindow auf, um das Handle des nächsten Steuerlements zu erhalten. Wir benutzen das GW_HWNDNEXT Flag um GetWindow mitzuteilen, dass das Handle des Fensters, welches als nächstes zum aktuellen hEdir steht, ermittelt werden soll. Wenn die Funktion NULL zurückliefert, interpretieren wir das als 'keine weiteren Handles zu ermitteln', so dass das aktuelle hEdit das letzter Steuerelement ist. Wir werden ein "wrap around" zum ersten Steuerelement ausführen, indem wir GetWindow mit dem GW_HWNDFIRST Flag aufrufen. Ähnlich wie im Tab-Fall arbeitet Shift-Tab nur halt andersherum.
Deutsche Übersetzung:
Joachim Rohde
Die original Win32Asm-Tutorials stammen von
Iczelion's Win32 Assembly HomePage