The MASM Forum

General => The Campus => Topic started by: newAsm on June 04, 2013, 07:36:21 PM

Title: Subclass button control
Post by: newAsm on June 04, 2013, 07:36:21 PM
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
Title: Re: Subclass button control
Post by: dedndave on June 04, 2013, 08:57:36 PM
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
Title: Re: Subclass button control
Post by: newAsm on June 04, 2013, 09:33:13 PM
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
Title: Re: Subclass button control
Post by: dedndave on June 04, 2013, 09:39:53 PM
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
Title: Re: Subclass button control
Post by: jj2007 on June 04, 2013, 10:32:13 PM
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.
Title: Re: Subclass button control
Post by: qWord on June 04, 2013, 11:55:31 PM
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.
Title: Re: Subclass button control
Post by: newAsm on June 05, 2013, 12:05:37 AM
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
Title: Re: Subclass button control
Post by: Dubby on June 05, 2013, 12:16:43 AM
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..
Title: Re: Subclass button control
Post by: newAsm on June 05, 2013, 12:18:45 AM
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

Title: Re: Subclass button control
Post by: dedndave on June 05, 2013, 12:19:25 AM
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.....
Title: Re: Subclass button control
Post by: dedndave on June 05, 2013, 12:22:53 AM
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
Title: Re: Subclass button control
Post by: qWord on June 05, 2013, 12:29:01 AM
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!
Title: Re: Subclass button control
Post by: dedndave on June 05, 2013, 12:29:23 AM
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
Title: Re: Subclass button control
Post by: newAsm on June 05, 2013, 12:39:23 AM
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


Title: Re: Subclass button control
Post by: dedndave on June 05, 2013, 12:46:14 AM
give this a try

Title: Re: Subclass button control
Post by: Dubby on June 05, 2013, 12:51:58 AM
hmm... let get this straight...

a button is indeed a window...

that's it...
please correct me if I'm wrong..
Title: Re: Subclass button control
Post by: qWord on June 05, 2013, 12:52:05 AM
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
Title: Re: Subclass button control
Post by: qWord on June 05, 2013, 12:53:58 AM
Quote from: Dubby on June 05, 2013, 12:51:58 AM
a button is indeed a window...
yes, more precisely a child window.
Title: Re: Subclass button control
Post by: newAsm on June 05, 2013, 01:02:05 AM
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
Title: Re: Subclass button control
Post by: dedndave on June 05, 2013, 01:15:27 AM
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
Title: Re: Subclass button control
Post by: Dubby on June 05, 2013, 01:20:46 AM
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..
Title: Re: Subclass button control
Post by: dedndave on June 05, 2013, 01:22:17 AM
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
Title: Re: Subclass button control
Post by: newAsm on June 05, 2013, 01:32:59 AM
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