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