Mouse events in the statusbar (subclassing)

Share your advanced PureBasic knowledge/code with the community.
User avatar
luis
Addict
Addict
Posts: 3895
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Mouse events in the statusbar (subclassing)

Post by luis »

Nothing really fancy, but it's simple and (I think) well organized so maybe can be useful for newcomers.

You can use this technique with any control and by responding to the right message implement what is missing natively in PB, in this case some messaging to let you know if you clicked / double clicked a field in the tray bar.

The number of the field were you clicked is also automatically determined without the need to hardcode the fields size in the routine.

OK, here it is.

Code: Select all

EnableExplicit

Macro LOWORD (word)
 (word & $FFFF)
EndMacro

Macro HIWORD (word)
 ((word >> 16) & $FFFF)
EndMacro

Enumeration
 #WIN_MAIN
 #SBG_STATUS
EndEnumeration

Procedure StatusBar_GrabClickEvents_Subclass (hWnd, Msg, wParam, lParam)
 Protected tRECT.RECT
 Protected k, X, Y
 Protected *fpFunc
 Protected *oldWinProc = GetProp_(hWnd, "oldWinProc")
 
 Select Msg
    
    Case #WM_NCDESTROY
        ; cleanup
        RemoveProp_(hWnd, "oldWinProc")
        RemoveProp_(hWnd, "callback")
 
    Case #WM_LBUTTONDBLCLK, #WM_RBUTTONDBLCLK, #WM_LBUTTONDOWN, #WM_RBUTTONDOWN
        X = LOWORD (lParam) ; X mouse coordinates
        Y = HIWORD (lParam) ; Y mouse coordinates
        *fpFunc = GetProp_(hWnd, "callback") ; callback address

        If *fpFunc > 0                       
            While SendMessage_(hWnd, #SB_GETRECT, k, @tRECT)              
                If X < tRECT\right 
                    ; call callback procedure
                    CallFunctionFast(*fpFunc, Msg, k, X, Y)
                    Break
                EndIf                                                
                k + 1
            Wend
        EndIf
  EndSelect
 
  ProcedureReturn CallWindowProc_(*oldWinProc, hWnd, Msg, wParam, lParam)   
EndProcedure


Procedure StatusBar_GrabClickEvents (hWnd, *fpFunc)
 SetProp_(hWnd, "oldWinProc", GetWindowLongPtr_(hWnd, #GWL_WNDPROC))   
 SetProp_(hWnd, "callback", *fpFunc)
 SetWindowLongPtr_(hWnd, #GWL_WNDPROC, @StatusBar_GrabClickEvents_Subclass())
EndProcedure


;  *** TEST ***

; this procedure process the click events

Procedure SBClickEvents (iMessage, iField, iXPos, iYPos)
 Select iMessage
    Case #WM_RBUTTONDOWN
        Debug "RMB pressed on " + Str(iField) + " at (" + Str(iXPos) + ", " + Str(iYPos) + ")"
    Case #WM_LBUTTONDOWN
        Debug "LMB pressed on " + Str(iField) + " at (" + Str(iXPos) + ", " + Str(iYPos) + ")"       
    Case #WM_LBUTTONDBLCLK
        Debug "LMB double clicked on " + Str(iField) + " at (" + Str(iXPos) + ", " + Str(iYPos) + ")"                       
    Case #WM_RBUTTONDBLCLK
        Debug "RMB double clicked on " + Str(iField) + " at (" + Str(iXPos) + ", " + Str(iYPos) + ")"                       
 EndSelect
EndProcedure


Procedure Main()
 Protected iEvent
 Protected hStatus
 
 If OpenWindow(#WIN_MAIN, 10, 10, 640, 480, "Main Window", #PB_Window_SystemMenu | #PB_Window_SizeGadget)
 
     hStatus = CreateStatusBar(#SBG_STATUS, WindowID(#WIN_MAIN))
     
     AddStatusBarField(100) ; width 100
     AddStatusBarField(200) ; width 200
     AddStatusBarField(#PB_Ignore)  ; autosize
     
     ; this call does the work
     StatusBar_GrabClickEvents (hStatus, @SBClickEvents())
     
     Repeat
        iEvent = WaitWindowEvent()           
     Until iEvent = #PB_Event_CloseWindow
   
 EndIf
 
EndProcedure

Main()
Last edited by luis on Mon Jul 20, 2009 9:23 pm, edited 2 times in total.
User avatar
Blue
Addict
Addict
Posts: 967
Joined: Fri Oct 06, 2006 4:41 am
Location: Canada

Re: Mouse events in the statusbar (subclassing)

Post by Blue »

Hi Luis
I was intrigued by the code you present here.
Your opening statement
luis wrote:Nothing really fancy, but it's simple and (I think) well organized so maybe can be useful for newcomers.
[...]
[/code]
got me interested. So i decided to try and figure out what you were doing and how.

I certainly enjoyed discovering how to properly use the SendMessage_() and other API calls, but, after playing around with your code a bit, I have to disagree with the statement quoted above :D

In particular, i wish to point out that ...

(1) The 2nd Case in your Procedure StatusBar_GrabClickEvents_Subclass is needlessly complicated. After all, it's a simple boudaries test!
The following works as well and is certainly much clearer:

Code: Select all

Procedure StatusBar_GrabClickEvents_Subclass (hWnd, msg, wParam, lParam) 
;Procedure GetClicks_Subclass (hWnd, msg, wParam, lParam) 
 Protected tRECT.RECT 
 Protected field, x1, x2, xMouse, yMouse 
 Protected *fpFunc, *oldWinProc = GetProp_(hWnd, "oldWinProc") 
  
 Select msg

    Case #WM_NCDESTROY 
        ; cleanup 
        RemoveProp_(hWnd, "oldWinProc") 
        RemoveProp_(hWnd, "callback") 

    Case #WM_LBUTTONDBLCLK, #WM_RBUTTONDBLCLK, #WM_LBUTTONDOWN, #WM_RBUTTONDOWN 
        xMouse = LOWORD (lParam)     ; X mouse coordinates 
        yMouse = HIWORD (lParam)     ; Y mouse coordinates 
        *fpFunc = GetProp_(hWnd, "callback") ; callback address 
                        
        While SendMessage_(hWnd, #SB_GETRECT, field, @tRECT)
            x1 = tRECT\left 
            x2 = tRECT\right 
            If xMouse > x1 And xMouse < x2 
              ; call callback procedure 
              If *fpFunc > 0 
                CallFunctionFast(*fpFunc, msg, field, xMouse, yMouse) 
              EndIf
              Break 
            EndIf 
            field + 1 
        Wend                    

  EndSelect 
  
  ProcedureReturn CallWindowProc_(*oldWinProc, hWnd, msg, wParam, lParam)    
EndProcedure
The  SendMessage_()  loop is leaner and simpler, wouldn't you agree ?
Plus, the modified variable names make the  If  statement quite explicit as to its purpose.

(2) in your example's Main Proc, you use a zero width to define an autosize field, but that should be #PB_Ignore to work properly.

I think the above modifications would make your contribution more useful to newcomers.
Last edited by Blue on Mon Jul 20, 2009 3:31 pm, edited 3 times in total.
PB Forums : Proof positive that 2 heads (or more...) are better than one :idea:
User avatar
luis
Addict
Addict
Posts: 3895
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: Mouse events in the statusbar (subclassing)

Post by luis »

Blue wrote: in your example's Main Proc, you use a zero width to define an autosize field, but that should be #PB_Ignore to work properly.
You are right, I will edit the above post to reflect that. I recall seeing the call used this way in the forum, and I thought 0 was the right value.
Maybe I misunderstood the original intent in that case too.

About the case, in part is more complicated because the use of 0 instead of #PB_ignore introduced a strange behaviour for the last field, that I trapped that way (the strange if). Using the correct #PB_ignore you can simplify the test.

Thanks for pointing out the error.
User avatar
luis
Addict
Addict
Posts: 3895
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

Updated.

Actually now (with autosized field's correct behaviour) the loop inside the case can be further semplified, see code.

Am I right ?

Thanks.


(sorry for the error people)
User avatar
Blue
Addict
Addict
Posts: 967
Joined: Fri Oct 06, 2006 4:41 am
Location: Canada

Post by Blue »

luis wrote:[...]Actually now (with autosized field's correct behaviour) the loop inside the case can be further semplified, see code.

Am I right ? [...]

Code: Select all

While SendMessage_(hWnd, #SB_GETRECT, k, @tRECT) 
     If X < tRECT\right And *fpFunc > 0 
          ; call callback procedure 
           CallFunctionFast(*fpFunc, Msg, k, X, Y) 
           Break 
     EndIf            
      k + 1 
Wend 
I'd say 'Right on, Baby!' if i knew you better, but i'll refrain my enthusiasm and simply comment that this code is now deserving of your initial presentation: it is clear and simple.

However, since we're having an intellectual exchange here, allow me to differ with you on 2 minor points:
(1) The variable k --> The SendMessage() loop iterates through the 3 fields on the statusBar. So why not call the variable field, which is what it actually is ? It's only a word, many would say, but since variables have been invented for the purpose of making code clearer, i think clarity should start with the choice and use of appropriate variable names.
It's very much like a ball. You can call it 'Round and soft bouncing sphere' if you please, and people will know what you're talking about (evenyually...) But calling it a ball is easier, and just makes watching the game more fun !

(2) I think the test for the validity of the function pointer should happen BEFORE entering the SendMessage() loop, immediately following the assignment to the variable *fpFunc . There's really no point in figuring out where the mouse pointer is if that function is nowhere to be found !!! And doing so makes the whole thing yet one more degree simpler.

PS: I'm curious about something:
I see that you check for the event #WM_RBUTTONDBLCLK.
Have you ever been able to generate (or trap) such an event?
No matter how much i try (or how furiously i right-click), i just never see this event happening...
PB Forums : Proof positive that 2 heads (or more...) are better than one :idea:
User avatar
luis
Addict
Addict
Posts: 3895
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Post by luis »

Maybe I should clear up my view of the tricks and tips section of the forum. Very often I found little pieces of code here useful, bright, or simply showing an approach I haven't think of.

Very often the code is not "nice" to see, the variables names are very different to what I'm used to or simply things like xxx, zzz. The style is different from mine. Heck, I NEVER do a copy and paste from someone else code!

Or the code is not optimized at all (and I totally agree on that). It's beyond the point. It's not a tutorial about "I suggest you to program this way, incidentally, MY way". It's an idea. A starting point to show you "it can be done this way, do you know that ?"

Usually is so. Sometimes the efficiency, the speed, the compactness is THE MESSAGE of the trick posted. So in those cases every line of code has a reson to be written that way and should be subject to a different level of analysis.

About the "k" variable: I certainly agree on principle. In practice I am used to define simple, short lived variables used as index in loops with names like k, j, and z. When I see a k floating around I know a couple of line before is used in a FOR or incremented a couple of line below before a WEND. I prefer that way.

The example is understandable anyway since is directed to "newcomers", yes but not to "braindead" people :)

But, by all means, I support the use of meaningful variables, so I understand your point.

About the test for the validity of the pointer to function:

The whole thing is meaningless if the pointer is null, the test is there as something not really required, but "shit can happen" so it's there just as a "hey, remember you could check if this is really ok". Anyway I modified the post to do the test at the upper level. :)

But it's only a simple source showing a way to do something (intercept gadget events and generate call to a procedure to process them). That was the intent. No need to beat it to death !

Very different, always in my view, was the error you signaled about the "PB_Ignore" instead of "0". That was absolutely relevant and I thank you for that. For some reason I was convinced 0 was the right value and I didn't check the docs, my fault!

blue wrote: PS: I'm curious about something:
I see that you check for the event #WM_RBUTTONDBLCLK.
Have you ever been able to generate (or trap) such an event?
No matter how much i try (or how furiously i right-click), i just never see this event happening...
I'm not sure I'm following you here.

Ehm... the subclassed proc redirect 4 events but the callback procedure process only 3 of them.

If you add a

Code: Select all

  Case #WM_RBUTTONDBLCLK
        Debug "RMB double clicked on " + Str(iField) + " at (" + Str(iXPos) + ", " + Str(iYPos) + ")"              
should work.

Is that what you are meaning ?

I added that to the example, just in case. The idea was to show "you can redirect n type of events, and then in your callback procedures process only what you like to process depending on your current needs".
Last edited by luis on Mon Jul 20, 2009 10:35 pm, edited 1 time in total.
User avatar
Blue
Addict
Addict
Posts: 967
Joined: Fri Oct 06, 2006 4:41 am
Location: Canada

Post by Blue »

point taken
PB Forums : Proof positive that 2 heads (or more...) are better than one :idea:
Post Reply