Iczelion - 22 - Fenster ableiten (Superclassing)

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