Tutorial 20: Window Subclassing
In this Tutorial, We Well Learn About Window Subclassing, What It is and how to use it to your advantage.
Download the Example
Here.
Theory: if you program in Windows for Some Time, You will Find Some Cases Where A Window HAS
nearly the attributes you need in your program but not quite. Have you encountered a situation where you want some special kind of edit control that can filter out some unwanted text? The straightforward thing to do is to code your own window. But it's really hard Work and time-consuming. Window Subclassing to the Rescue.
In a nutshell, window subclassing allows you to "take over" the subclassed window. You will have absolute control over it. Let's take an example to make this clearer. Suppose you need a text box that accepts only hex numbers. If you use a simple edit control, you have no say whatsoever when your user types something other than hex numbers into your text box, ie. if the user types "zb q *" into your text box, you can not do anything with it except rejecting The whole text string. this is
Unprofessional at Least. in Essence, You NEED The Ability To Examine Each Character The User Typed Into The Text Box Right At The Moment He Typed IT.
We will examine how to do that now. When the user types something into a text box, Windows sends WM_CHAR message to the edit control's window procedure. This window procedure resides inside Windows itself so we can not modify it.
But we can redirect the message flow to our own window procedure. So that our window procedure will get first shot at any message Windows sends to the edit control. If our window procedure chooses to act on the message, it can do so. But if it does not want to handle the message, it can pass it to the original window procedure This way, our window procedure inserts itself between Windows and the edit control Look at the flow below:.. Before SubclassingWindows ==> edit control's window procedure
After SubclassingWindows ==> Our window procedure -----> Edit Control's WINDOW Procedure
NOW We Put Our Attention On How To Subclass A Window. Note That Subclassing Is Not Limited to Controls, IT CAN Be.com.
Let's think about how Windows knows where the edit control's window procedure resides. A guess? ...... lpfnWndProc member of WNDCLASSEX structure. If we can replace this member with the address of our own window procedure, Windows will send messages to our Window Proc Instead.
We can do this by calling setwindowlong.
Setwindowlong Proto Hwnd: DWORD, NINDEX: DWORD, DWNEWLONG: DWORD
HWnd = Handle of the window to change the value in the Wndclassex Structure
Nindex == Value to change.
GWL_EXSTYLE STS A New Extended Window Style.
GWL_Style Sets a new window style.
GWL_WNDPROC STS A New Address for the Window ProCedure.
GWL_HINSTANCE SETS A New Application Instance Handle.
GWL_ID SETS A New Identifier of The Window.
GWL_USERDATA STS The 32-bit value associated with the window. Each window HAS a Corresponding 32-bit value intended for use by the application That created the window.
Dwnewlong = The Replacement Value.
So our job is easy: We code a window proc that will handle the messages for the edit control and then call SetWindowLong with GWL_WNDPROC flag, passing along the address of our window proc as the third parameter If the function succeeds, the return value is. the previous value of the specified 32-bit integer, in our case, the address of the original window procedure. We need to store this value for use within our window procedure.Remember that there will be some messages we do not want to handle We will pass Them to the Original Window Procedure. We can do what by calling callwindowproc function.
CallWindowProc Proto LPPREVWNDFUNC: DWORD, /
HWND: DWORD, /
MSG: DWORD, /
WPARAM: DWORD, /
LParam: DWORD
LPPREVWNDFUNC = The address of the original window procedure.
The Remaining Four Parameters Are The ones Passed To Our Window Procedure. We Just Pass Them Along To CallWindowProc.
Code Sample:
.386
.Model flat, stdcall
Option CaseMAP: NONE
INCLUDE /MASM32/INCLUDE/Windows.inc
INCLUDE /MASM32/INCLUDE/USER32.INC
INCLUDE /MASM32/INCLUDE / WANEL32.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 "You Pressed Enter in the text box!", 0
.DATA? Hinstance Hinstance? HWndEt 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 .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 Hwndit, Eax Invoke setfocus, EAX; ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -----; Subclass it! ;; -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 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
EditWordProc 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 startAnalysis:
Invoke setWindowlong, HWndIt, GWL_WndProc, Addr EditWndProc
Mov OldWndProc, EAX
After the edit control is created, we subclass it by calling SetWindowLong, replacing the address of the original window procedure with our own window procedure. Note that we store the address of the original window procedure for use with CallWindowProc. Note the EditWndProc is an ordinary Window procedure.
.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
Within EditWndProc, we filter WM_CHAR messages. If the character is between 0-9 or af, we accept it by passing along the message to the original window procedure. If it is a lower case character, we convert it to upper case by adding it With 20h. Note That, IF The Character Is Not The ORIGINAL WINDOW PASS SOS NOTHER THEN 0-9 OR AF, The Character Just Doesn 'T Appear in the Edit Control.
.ELSEIF uMSG == WM_KEYDOWN
Mov Eax, WPARAM
.IF AL == VK_RETURN
Invoke Messagebox, Hedit, Addr Message, Addr Appname, MB_OK MB_ICONITIONFORMATION
Invoke setfocus, Hedit
.lse
Invoke CallWindowProc, OldwndProc, Hedit, UMSG, WPARAM, LPARAM
RET
.end .end
I want to demonstrate the power of subclassing further by trapping Enter key. EditWndProc checks WM_KEYDOWN message if it's VK_RETURN (the Enter key). If it is, it displays a message box saying "You pressed the Enter key in the text box!". If it's not an enter key, it passes the message to the Original Window Procedure.
You can Use Window Subclassing to Take Control over Other Windows. It's one of the powerful techniques You Should Have in Your Arsnal.