Iczelion - 20 - Fenster ableiten (Subclassing)

Tutorial 20: Fenster ableiten (Window Subclassing)


In diesem Tutorial werden wir etwas über's Fenster ableiten (window subclassing) lernen, was es ist und wie Sie es zu Ihrem Vorteil nutzen können.
Laden Sie das Beispiel hier herunter.

Anmerkung des Übersetzers: Mir ist leider keine deutsche Terminologie für Subclassing und Superclassing geläufig. Beides habe ich mit ableiten/Ableitung übersetzt, auch wenn beides definitiv nicht gleichzusetzen ist! Wenn Sie diese Tutorial und Tutorial 22 durchgelesen habe, wird Ihnen der Unterschied bewusst werden. Für irgendwelche Ideen zu diesem Thema wäre ich sehr dankbar.

Theorie:

Wenn Sie einige Zeit Windows-Programme schreiben, werden Sie dem Fall begegnen, dass ein Fenster fast die selben Attribute hat, die Sie in Ihrem Programm benötigen, aber doch nicht ganz die selben. Sind Sie mal der Situation begegnet, wo Sie eine spezielle Art von Edit-Steuerelement brauchten, wo Sie ungewünschten Text herausfiltern wollten? Die direkteste Art wäre ein eigenes Fenster zu programmieren. Aber es ist harte Arbeit und zeitaufwendig. Fenster abzuleiten ist die Rettung.
Kurz gesagt erlaubt Ihnen das Ableiten von Fenstern die "Kontrolle" über das abgeleitete Fenster zu übernehmen. Sie werden absolute Kontrolle darüber haben. Nehmen wir ein Beispiel zu Hilfe, um das zu verdeutlichen. Nehmen wir an, Sie brauch eine Text-Box, die nur Hexadzezimale Ziffern akzeptiert. Wenn Sie ein einfaches Edit Steuerelement benutzen, haben Sie keinen Einfluss darauf, was der Benutzer eintippt. Wenn der Benutzer zum Beispiel "zb+q*" eingibt, können Sie nichts machen, außer den kompletten String zu verwerfen. Das ist ziemlich unprofessional. Im wesentlichen brauchen Sie die Möglichkeit, jeden Buchstaben, den der Benutzer eintippt, genau im selben Moment zu untersuchen.
Wir werden uns anschauen, wie man so was macht. Wenn der Benutzer etwas in eine Text Box eintippt, sendet Windows eine WM_CHAR Nachricht an die Edit-Steuerlement-Fenster-Prozedur. Diese Fenster-Prozedur wird von Windows selbst zu verfügung gestellt, so dass wir diese nicht verändern können. Aber wir können den Nachrichten-Fluss auf unsere eigene Fenster-Prozedur umleiten. So, dass unsere Fenster-Prozedur als erstes jegliche Nachricht, die Windows sendet, erhält. Wenn unsere Fenster-Prozedur meint, diese Nachricht bearbeiten zu müssen, kann sie das tun. Wenn sie aber die Nachricht nicht behandeln möchte, kann sie sie an die original Fenster-Prozedur weiterleiten. Auf diese Weise fügt sich unsere Fenster-Prozedur selbst zwischen Windows und dem Edit-Steuerelement ein. Schauen wir uns den Fluss an:

    Vor dem Ableiten

    Windows ==> Edit Steuerelement Fenster-Prozedur

    Nach dem Ableiten

    Windows ==> unsere Fenster-Prozedur -----> Edit Steuerelement Fenster-Prozedur
Nun lenken wir unsere Aufmerksamkeit darauf, wie man ein Fenster ableitet. Beachten Sie, dass das Ableiten nicht auf Steuerelemente beschränkt ist, es kann auf jedes Fenster angewandt werden.
Lassen Sie uns darüber nachdenken, woher Windows weiß, wo sich die Edit-Steuerelement-Fenster-Prozedur befindet. Irgend eine Idee?......das lpfnWndProc Element der WNDCLASSEX Struktur. Wenn wir dieses Element mit der Adresse unserer eigenen Fenster-Prozedur ersetzen, sendet Windows die Nachrichten an unsere Fenster-Prozedur.
Wir können das erreichen, indem wir SetWindowLong aufrufen.
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD


hWnd = Handle des Fenster, dessen Wert in der WNDCLASSEX Struktur geändert werden soll
nIndex == zu ändernter Wert.
    GWL_EXSTYLE Setzt einen neuen erweiterten Fenster Stil.
    GWL_STYLE Setzt einen neuen Fenster Stil.
    GWL_WNDPROC Setzt eine neue Adresse der Fenster-Prozedur.
    GWL_HINSTANCE Setzt ein neues Applikations Handle.
    GWL_ID Setzt einen neuen Identifizierer des Fensters.
    GWL_USERDATA Setzt einen 32-Bit Wert, der dem Fenster zugehörig ist. Jedes Fenster hat einen korrespondierenden 32-Bit Wert, der für den Gebrauch seitens der Applikation vorgesehen ist, die das Fenster erzeugt hat.
dwNewLong = der ersetzende Wert.
Nun ist unsere Aufgabe leicht: wir programmieren eine Fenster-Prozedur, die die Nachrichten für das Edit-Steuerelement behandelt und rufen dann SetWindowLong mit dem GWL_WNDPROC Flag auf, übergeben die Adresse unserer Fenster-Prozedur als dritten Parameter. Wenn die Funktion erfolgreich war, ist der Rückgabewert der vorherige spezifizierte 32-Bit Wert, in unserem Fall, die Adresse der original Fenster-Prozedur. Wir müssen diesen Wert speichern, um ihn in unserer Fenster-Prozedur zu benutzen.
Errinnern Sie sich, dass einige Nachrichten auftauchen werden, die wir nicht behandeln wollen, wir werden diese an die original Fenster-Prozedur weiterleite. Wir können das machen, indem wir die CallWindowProc Funktion aufrufen.
CallWindowProc PROTO lpPrevWndFunc:DWORD, \ hWnd:DWORD,\ Msg:DWORD,\ wParam:DWORD,\ lParam:DWORD


lpPrevWndFunc = die Adresse der original Fenster-Prozedur.
Die übrigen vier Parameter werden an unsere Fenster Prozedur weitergegeben. Wir reichen Sie nur an CallWindowProc weiter.

Code Beispiel:

.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 "Subclassing Demo",0 EditClass db "EDIT",0 Message db "Sie haben Enter in der Text Box gedrückt!",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,200,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 ;----------------------------------------- ; Leite es ab! ;----------------------------------------- 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


Analyse:

invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc mov OldWndProc,eax


Nachdem das Edit Steuerelement erzeugt wurde, leiten wir es ab, indem wir SetWindowLong aufrufen und die Adresse der Original Fenster-Prozedur mit unserer eigenen Fenster-Prozedur ersetzen. Beachten Sie, dass wir die Adresse der Original Fenster-Prozedur sichern um sie mit CallWindowProc zu benutzen. Beachten Sie, dass EditWndProc eine ordinäre Fenster-Prozedur ist.
.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


Innerhalb von EditWndProc filtern wir die WM_CHAR Nachrichten. Wenn der Buchstabe zwischen 0-9 oder a-f ist, akzeptieren wir das und leiten die Nachricht an die Original Fenster-Prozedur weiter. Wenn es ein Kleinbuchstabe ist, konvertieren wir ihn zum Großbuchstaben, indem wir 20h addieren. Beachten Sie, dass, wenn der Buchstabe nicht der ist, den wir erwartet haben, dass wir ihn verwerfen. Wir leiten ihn nicht an die original Fenster-Prozedur weiter. Wenn der Benutzer etwas anderes als 0-9 oder a-f eingibt, erscheint der Buchstabe einfach nicht im Edit-Feld
.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 .end


Ich möchte die Macht der Ableitung weiter durch das Abfangen der Enter-Taste demonstrieren. EditWndProc überprüft WM_KEYDOWN Nachrichten, ob sie VK_RETURN (die Enter Taste) enthalten. Wenn ja, wird eine Message Box angezeigt, die sagt, dass die Enter Taste gedrückt wurde. Wenn es nicht die Enter Taste war, wird die Nachricht an die original Fenster-Prozedur weitergeleitet.
Sie können Fenster Ableitungen benutzen um Kontrolle über andere Fenster zu erlangen. Es ist eine mächtige Technik, die Sie in ihrem Reportoire haben sollten.

Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage