Hi,
I am learning how to subclass a control. I have read some of the examples and I think I have followed the mechanism to subclass, using SetWindowLong. The subclassed routine is never called, only the button event in the main winProc is called. Attached us my example and do please comment of my mistakes. Thanks ..newAsm
i didn't do a thorough read, but i do see some problems with regard to use of registers
http://masm32.com/board/index.php?topic=1989.msg20743#msg20743 (http://masm32.com/board/index.php?topic=1989.msg20743#msg20743)
as explained in that post, EBX, EBP, ESI, and EDI should be preserved inside WndProc
because you already have the code written, a simple fix might be....
WndProc proc USES EBX ESI hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
this code presents another problem
Case WM_COMMAND
.if eax == WM_COMMAND
mov ebx,wParam
and ebx,0FFFFh
shr ebx,16
cmp bx,BN_CLICKED ; button clicked
first, "Case WM_COMMAND" says "execute this code if uMsg is WM_COMMAND"
the next line, ".if eax.....", will never compare true because EAX does not contain the uMsg value
in any case, the 2 lines are redundant
just remove the ".if eax...." line to fix that one
second, for the WM_COMMAND message, wParam is split into low and high words
http://msdn.microsoft.com/en-us/library/windows/desktop/ms647591%28v=vs.85%29.aspx (http://msdn.microsoft.com/en-us/library/windows/desktop/ms647591%28v=vs.85%29.aspx)
when the message is received from a control, the high word has the notification code and the low word has the control ID
your code tests BN_CLICKED, but never checks the control ID
you could do it this way
movzx eax,word ptr wParam[2] ;EAX = wParam high word, zero extended
movzx edx,word ptr wParam ;EDX = wParam low word, zero extended
.if eax==BN_CLICKED
.if edx==100 ;button control ID = 100
;do button stuff here
.endif
.endif
as it happens, the value for BN_CLICKED is 0 :biggrin:
so really, you only need to compare wParam (as a dword) to the button identifier number
if the value of the high word is something other than BN_CLICKED, that test will compare false
.if wParam==100 ;(button control ID = 100) + (BN_CLICKED * 65536)
;do button stuff here
.endif
the code may have other issues, but that should help you get going
Thanks dedndave,
I tried your suggestion and I made some changes to both winProc and procBtnCtrl. The program does not work now. The issue is probably I may not understand properly. I have issue using
movzx eax,wparam+2
The assembler issued an error. As a result,
I changed it to:
case WM_COMMAND
mov eax,wParam ;
mov edx,eax
shr eax,16
and edx,0FFFFh
;cmp bx,BN_CLICKED ; button clicked
.if eax == BN_CLICKED
cmp eax,100 ; button clicked?
jne Noclick ; no, Exit
invoke MessageBox,hWnd,ADDR s_MClicked, ADDR szDisplayName,MB_OKCANCEL
;return 0
.endif
;=================================================================
for procBtnBtrl
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
procBtnCtrl proc hwnd:DWORD, uMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,uMsg
.if uMsg == WM_COMMAND
mov eax,wParam ;
mov edx,eax
shr eax,16
and edx,0FFFFh
cmp ax,BN_CLICKED ; button clicked
jne procBtnOut ; no, Exit
cmp dx,100
jne procBtnOut
invoke MessageBox,hWnd,ADDR s_Clicked, ADDR szDisplayName,MB_OKCANCEL
;return 0
.endif
procBtnOut:
invoke CallWindowProc,lpBtnProc ,hwnd,uMsg,wParam,lParam
ret
procBtnCtrl endp
Did I do something wrong? I intend to subclass the button to have it own event handler. Thanks appreciate your quick response.
newAsm
sorry about that
it should be
movzx eax,word ptr wParam+2
or
movzx eax,word ptr wParam[2]
the "word ptr" is required (and on the EDX instruction, also)
C:\Masm32\Examples\exampl01\bmbutton
it's a simple example of a subclassed button control
You can also do direct mem to immediate comparisons:
Case WM_COMMAND
; movzx eax, word ptr wParam+2 ; use if you prefer to have high and
; movzx edx, word ptr wParam ; low words in registers
.if word ptr wParam+2 == BN_CLICKED
cmp word ptr wParam, 100 ; button clicked?
jne Noclick ; no, Exit
MsgBox 0, "You clicked the button", "Button main proc:", MB_OK
;return 0
.endif
... or, shorter:
Case WM_COMMAND
.if word ptr wParam+2 == BN_CLICKED && word ptr wParam==100
MsgBox 0, "You clicked the button", "Button main proc:", MB_OK
.endif
MsgBox is a Masm32 macro aimed at improving the readability of your code.
Just as side note: if I'm not wrong, the class style CS_BYTEALIGNWINDOW and CS_BYTEALIGNCLIENT have no meaning on nowadays computers. The flags are only useful when the color depth is 1 or 4 bits. In that case the flags modifies the position, width and height in such way that the corresponding regions lies on a byte boundaries and have width/height of N*8 bits - this allows (for example) byte wise copying without the need of expensive bit manipulation.
Thanks dedndave, qWord, & ji2007,
I will look at the example and will update the information you have all kindly so provided.
On last question, what was wrong with my subclass function (procBtnCtrl). In WM_CREATE, even though SetWindowLong was invoked, the sub-classed callback function was not called. Did I do the SetWindowLong correctly?
Thanks you very much.
newAsm
As far as I know, the WM_CREATE is called whenever the control is created.. if you want to intercept it, you'll need superclassing..
Hi there,
I looked through bmbutton in \masm32\examples\exampl02 directory. Just to check with you on this button clicked:
1. Which is better to use, BN_CLICKED, or WM_LBUTTONDOWN or WM_LBUTTONUP?
2. Do they mean the same?
3. For any button click, as in most button controls, the click tend to be left button. Would WM_LBUTTONDOWN or up be better?
Thanks..newAsm
he's got it right
that part looks like it should be ok
qWord....
the byte-align thing is a throw-back to Iczelion days :P
parts of the code were copied from Hutch's ProStart program code
give me a little time to look over the code more thoroughly.....
for a button, you are going to get WM_COMMAND/BN_CLICKED
WM_LBUTTONDOWN and WM_LBUTTONUP are useful if you want to detect the mouse click directly, in the client area
notice that the window client area is handled in WndProc
the button is actually a window on it's own, and has it's own WndProc to detect mouse clicks
if the mouse is over the button, the click messages go to the WndProc for the buttons
Even not a problem, but the DefWindowProc is misplaced in WndProc: it should be placed in the default case of the switch/case block.
The subclass does work. However, the WM_COMAND in procBtnCtrl is useless, because controls does not send notifications to themselves - they notify their parent instead.
The problem for the WndProc is:
cmp eax,100 ; button clicked?
it should be EDX!
i see a basic problem - that is that your WndProc must return a result to the OS
the result may be different for different messages, but is most often 0
if you do not return 0 from WM_CREATE, window creation is aborted - and you have no window
Hi qWord,
Thanks for your eagle eye - you are right. I corrected it.
After I created the button in WM_CREATE, save the button handle, and then invoke SetWindowLong to subclass the button, the subclassed button callback routine is never called. The main WndProc click is activated. I return a zero but the main WndProc is called. Attached is the updated program.
In bmbutton, WM_LBUTTONUP is used in the button callback routine and not in the main WndProc routine.
Thanks..newAsm
give this a try
hmm... let get this straight...
a button is indeed a window...
that's it...
please correct me if I'm wrong..
nearly the same as dave's, but marks the errors/changes:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
.486 ; create 32 bit code
.model flat, stdcall ; 32 bit memory model
option casemap :none ; case sensitive
include Button.inc ; local includes for this file
procBtnCtrl PROTO :DWORD, :DWORD, :DWORD, :DWORD
.code
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; ------------------
; set global values
; ------------------
mov hInstance, FUNC(GetModuleHandle, NULL)
mov CommandLine, FUNC(GetCommandLine)
mov hIcon, FUNC(LoadIcon,hInstance,500)
mov hCursor, FUNC(LoadCursor,NULL,IDC_ARROW)
mov sWid, FUNC(GetSystemMetrics,SM_CXSCREEN)
mov sHgt, FUNC(GetSystemMetrics,SM_CYSCREEN)
call Main
invoke ExitProcess,eax
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
Main proc
LOCAL Wwd:DWORD,Wht:DWORD,Wtx:DWORD,Wty:DWORD
STRING szClassName,"Prostart_Class"
; --------------------------------------------
; register class name for CreateWindowEx call
; --------------------------------------------
invoke RegisterWinClass,ADDR WndProc,ADDR szClassName,
hIcon,hCursor,COLOR_BTNFACE+1
; -------------------------------------------------
; macro to autoscale window co-ordinates to screen
; percentages and centre window at those sizes.
; -------------------------------------------------
AutoScale 75, 70
invoke CreateWindowEx,WS_EX_LEFT,
ADDR szClassName,
ADDR szDisplayName,
WS_OVERLAPPEDWINDOW,
Wtx,Wty,Wwd,Wht,
NULL,NULL,
hInstance,NULL
mov hWnd,eax
; ---------------------------
; macros for unchanging code
; ---------------------------
DisplayWindow hWnd,SW_SHOWNORMAL
call MsgLoop
ret
Main endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
RegisterWinClass proc lpWndProc:DWORD, lpClassName:DWORD,
Icon:DWORD, Cursor:DWORD, bColor:DWORD
LOCAL wc:WNDCLASSEX
mov wc.cbSize, sizeof WNDCLASSEX
mov wc.style, CS_BYTEALIGNCLIENT or \
CS_BYTEALIGNWINDOW
m2m wc.lpfnWndProc, lpWndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
m2m wc.hInstance, hInstance
m2m wc.hbrBackground, bColor
mov wc.lpszMenuName, NULL
m2m wc.lpszClassName, lpClassName
m2m wc.hIcon, Icon
m2m wc.hCursor, Cursor
m2m wc.hIconSm, Icon
invoke RegisterClassEx, ADDR wc
ret
RegisterWinClass endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
MsgLoop proc
LOCAL msg:MSG
push esi
push edi
xor edi, edi ; clear EDI
lea esi, msg ; Structure address in ESI
jmp jumpin
StartLoop:
invoke TranslateMessage, esi
; --------------------------------------
; perform any required key processing here
; --------------------------------------
invoke DispatchMessage, esi
jumpin:
invoke GetMessage,esi,edi,edi,edi
test eax, eax
jnz StartLoop
mov eax, msg.wParam
pop edi
pop esi
ret
MsgLoop endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; "uses esi edi ebx" doesn't hurt!
WndProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL var :DWORD
LOCAL caW :DWORD
LOCAL caH :DWORD
LOCAL Rct :RECT
LOCAL buffer1[260]:BYTE ; these are two spare buffers
LOCAL buffer2[260]:BYTE ; for text manipulation etc..
Switch uMsg
Case WM_COMMAND
movzx eax,word ptr wParam[2] ;
movzx edx,word ptr wParam
;shr eax,16 ;<----------------
;and edx,0FFFFh ;<----------------
;cmp bx,BN_CLICKED ; button clicked
.if eax == BN_CLICKED
cmp edx,100 ; button clicked?
jne Noclick ; no, Exit
invoke MessageBox,hWnd,ADDR s_MClicked, ADDR szDisplayName,MB_OKCANCEL
;return 0
sub eax,eax
ret
.endif
Noclick:
Case WM_CREATE
invoke CreateWindowEx,0,offset ButtonClass,offset pszClass,\
WS_CHILD or WS_VISIBLE, \
100,100,60,30,hWin,100,
hInstance,NULL
mov [hBtn],eax
;lea esi,procBtnCtrl ; <---------------- WinABI!
INVOKE SetWindowLong, eax, GWL_WNDPROC, procBtnCtrl
mov [lpBtnProc],eax
Case WM_SYSCOLORCHANGE
Case WM_SIZE
Case WM_CLOSE
invoke DestroyWindow,hWin ; <----------------
Case WM_DESTROY
invoke PostQuitMessage,NULL
Default
invoke DefWindowProc,hWin,uMsg,wParam,lParam ; <-------
ret ;<-------
Endsw
xor eax,eax ;<--------
ret
WndProc endp
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
OPTION PROLOGUE:NONE
OPTION EPILOGUE:NONE
TopXY proc wDim:DWORD, sDim:DWORD
mov eax, [esp+8]
sub eax, [esp+4]
shr eax, 1
ret 8
TopXY endp
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
procBtnCtrl proc hwnd:DWORD, uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg == WM_LBUTTONDOWN
invoke MessageBeep,MB_OK
.endif
invoke CallWindowProc,lpBtnProc ,hwnd,uMsg,wParam,lParam
ret
procBtnCtrl endp
end start
Quote from: Dubby on June 05, 2013, 12:51:58 AM
a button is indeed a window...
yes, more precisely a child window.
Hi dedndave,
You example worked :greenclp:Thanks. I heard a short beep and the message.
One question, in my example BN_CLICKED was not recognized.
You used WM_LBUTTONDOWN and it worked. I will experiment with BN_CLICKED using your example but I am curious as where did I do wrong. Was it due to the notification type or what?
Appreciate your example and help. It has been very helpful and now I have to seriously consider using WM_LBUTTONDOWN than BN_CLICKED.
Thanks..newAsm
i used WM_LBTNDOWN in the subclassed button proc
i used WM_COMMAND/BN_CLICKED in WndProc
the BN_CLICKED is assumed, as i mentioned before - BN_CLICKED=0
Hi newAsm,
please allow me to elaborate my previous statement..
according to the MSDN BN_CLICKED is notification message sent by a button (a child window) to it's parent window.
and about GWL_WNDPROC, according to MSDN the GWL_WNDPROC Sets a new address for the window procedure.
thus, by subclassing a button (replacing the procedure of a button), now you have a full control to act as a button itself...
the BN_CLICKED notification message only exist in parent window, because the child window (button) sent it to the parent.
hope this helps..
let me clarify that a little....
mov eax,uMsg
.if eax==WM_COMMAND
.if wParam==CID_BUTTON1
INVOKE MessageBox,hWin,ADDR s_MClicked, ADDR szDisplayName,MB_OKCANCEL
.endif
xor eax,eax ;return 0
.elseif eax==WM_CREATE
to be "more correct", that should read
.if wParam==CID_BUTTON1 + (65536*BN_CLICKED)
because BN_CLICKED = 0, we can leave the "+ (65536*BN_CLICKED)" part out :P
Thanks Dubby for the clarification. Sometimes, it is a bit confusing to think that a button is a window.
Thanks dedbdave and qWord for your "guru" help and example. :greenclp:
..newAsm