Page 1 of 2

#WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 8:03 am
by BarryG
Just upgraded from 6.01 to 6.02 in the Windows version, and this part of my window callback no longer generates a #WM_NOTIFY message:

Code: Select all

Procedure AppCallback(hWnd,Message,wParam,lParam)

  result=#PB_ProcessPureBasicEvents
  
  If Message=#WM_NOTIFY
  
    Debug "This is not seen in 6.02, but is for 6.01"
  
  ElseIf Message=#WM_HOTKEY
  
    [...]
Obviously I can't paste the whole code, but the message above doesn't show in 6.02 but does in 6.01. How can I even start to debug this? Or is it a bug? After all, if it notifies in 6.01, then it also should in 6.02, right? No code changes.

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 8:35 am
by fryquez
There are some breaking changes with 6.02 and PanelGadgets, maybe also ContainerGadget.
WM_NOTIFY should be passed to the parent hWnd, but earlier version passed it to main window.
So it was very easy getting the messages with your SetWindowCallback.

With 6.02 you will need to subclass the actual parent of the hWnd that generates the message.

For debugging WM_Messages SpyEx is very useful.

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 9:04 am
by BarryG
This change has affected all my CalendarGadgets, ListIconGadgets, and PanelTabs. Basically #WM_NOTIFY isn't firing for any of those now, and thus my app is totally broken. Looks like I'll just stick with 6.01 forever, as it's too much work to investigate and fix it all. Dang.

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 9:19 am
by Fred
You can check these topics: viewtopic.php?t=81573 and viewtopic.php?t=81568 . It's shouldn't be that difficult, you should basically change SetWindowCallback() by SetWindowLongPtr_() and it should work like before.

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 10:39 am
by mk-soft
I had once programmed something to simplify it. Would also be a features request

SetGadgetCallback

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 11:19 am
by BarryG
Basically, here's the part of my window callback that doesn't work with 6.02 at all. (Obviously I've left out some stuff for privacy). How can these be made to work with 6.02 then? All the gadgets are children of a PanelGadget. Any help appreciated, as it's way out of my league. Even just getting #WM_NOTIFY to be triggered would probably solve the problem.

Code: Select all

result=#PB_ProcessPureBasicEvents

If Message=#WM_NOTIFY ; Never triggers on 6.02, but does on 6.01. Seems to be the whole problem?
  
  *pnmh.NMHDR=lParam
  gadid=*pnmh\hwndFrom
  
  Select *pnmh\code
      
    Case #LVN_COLUMNCLICK ; ListIconGadget header click.
      
      Select gadid
        Case blah1 : ; Do something now that this header was clicked.
        Case blah2 : *NMLV.NMLISTVIEW=lParam : If *NMLV\iSubItem=1 : globalvar=1 : EndIf
      EndSelect
      
    Case #HDN_BEGINTRACK ; Stop user resizing 0-width (hidden) ListIcon columns.
      
      *lpnm.NMHEADER=lParam
      type=*lpnm\hdr\code
      
      If type=#HDN_BEGINTRACK Or type=#HDN_DIVIDERDBLCLICK Or type=#HDN_ITEMCHANGING
        If *lpnm\hdr\hwndFrom=GetWindow_(GadgetID(#gad1),#GW_CHILD) And *lpnm\iItem=#gad1 : result=1
        ElseIf *lpnm\hdr\hwndFrom=GetWindow_(GadgetID(#gad2),#GW_CHILD) And *lpnm\iItem=>#gad2 : result=1
        EndIf
      EndIf
      
    Case #TCN_SELCHANGING ; Panel tab changing.
      
      ; Do something.
      
    Case #TCN_SELCHANGE ; Panel tab finished changing.
      
      ; Finished doing something.
      
    Case #MCN_FIRST+1 ; = #MCN_SELCHANGE (needed to get events from calendar/date gadgets after 19 Jan 2038).
      
      ; CalendarGadget do something.
      
    Case #LVN_DELETEALLITEMS ; Fast clear ListIconGadget.
      
      result=1
      
  EndSelect
  
EndIf

ProcedureReturn result
Note: I tried putting this code into its own procedure and calling it with this:

Code: Select all

Global MyPanelCallback

Procedure PanelCallback(hWnd,Message,wParam,lParam)
  [...]
  ProcedureReturn CallWindowProc_(MyPanelCallback,hWnd,Message,wParam,lParam)
  ; But I really need "ProcedureReturn result" here, to cancel ListIcon column width changing, etc.
EndProcedure

MyPanelCallback=SetWindowLongPtr_(GadgetID(#PanelGadget),#GWLP_WNDPROC,@PanelCallback())
But it still didn't trigger the #WM_NOTIFY message. I'm lost.

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 12:13 pm
by Axolotl
Hi BarryG,

the PanelGadget() needs its own callback with 6.02.
The old behavior was more or less a lucky coincidence (as fred called it).

Look at this example...
viewtopic.php?p=600962#p600962

BTW: And yes, ContainerGadget() does not work as well, but I haven't used ContainerGadget() so I was not motivated to find a working solution...

Happy coding and stay healthy.

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 12:16 pm
by Fred
@BarryG, you need to bind it to each PanelItem, that's why it doesn't trigger. I agree than a SetGadgetCallback() could be useful, if it also bind the parent gadget to get the NOTIFY message.

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 4:32 pm
by Axolotl
Okay, because of another question about ContainerGadget() ....
I figured something out
viewtopic.php?p=601614#p601614

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 5:05 pm
by Paul
Procedure PanelCallback(hWnd,Message,wParam,lParam)
[...]
ProcedureReturn CallWindowProc_(MyPanelCallback,hWnd,Message,wParam,lParam)
; But I really need "ProcedureReturn result" here, to cancel ListIcon column width changing, etc.
EndProcedure
I have the same question, how do you deal with ProcedureReturn result when subclassing?

How would I convert something like this?
Compile under 6.01 to see what it is suppose to look like.

Code: Select all

#Window_Main=1
#Gadget_Main_Panel=1
#Gadget_Main_Tab1=2
#Gadget_Main_List=3


Procedure WindowCallback(WindowID,Message,wParam,lParam)
  Shared hRowSelected,hRow
  Protected ReturnValue=#PB_ProcessPureBasicEvents
  
  Select Message
    Case #WM_NOTIFY 
      *LVCDHeader.NMLVCUSTOMDRAW=lParam           
      If *LVCDHeader\nmcd\hdr\code=#NM_CUSTOMDRAW
        If *LVCDHeader\nmcd\hdr\hwndFrom=GadgetID(#Gadget_Main_List)
          Select *LVCDHeader\nmcd\dwDrawStage 
            Case #CDDS_PREPAINT 
              ReturnValue=#CDRF_NOTIFYITEMDRAW 

            Case #CDDS_ITEMPREPAINT 
              hRowSelected=#False 
              hRow=*LVCDHeader\nmcd\dwItemSpec   
              If GetGadgetItemState(#Gadget_Main_List,hRow) & #PB_ListIcon_Selected
                hRowSelected=#True
                *LVCDHeader\clrText=$FFFFFF
                Else
                *LVCDHeader\clrText=$000000
              EndIf
              
              If hRowSelected
                SetGadgetItemState(#Gadget_Main_List,hRow,0)
                *LVCDHeader\clrTextBk=$000000
                ReturnValue=#CDRF_NEWFONT|#CDRF_NOTIFYPOSTPAINT
                Else                  
                If (hRow/2)*2=hRow
                  *LVCDHeader\clrTextBk=$DFFFE7
                  Else
                  *LVCDHeader\clrTextBk=$F3FFF6                                        
                EndIf
                ReturnValue=#CDRF_NEWFONT
              EndIf
              
            Case #CDDS_ITEMPOSTPAINT
              If hRowSelected
                SetGadgetItemState(#Gadget_Main_List,hRow,#PB_ListIcon_Selected)
              EndIf
              ReturnValue=#CDRF_DODEFAULT

          EndSelect        
        EndIf
      EndIf    
    
  EndSelect  
  
  ProcedureReturn ReturnValue
EndProcedure


If OpenWindow(#Window_Main,0,0,759,458,"Panel Test",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
  PanelGadget(#Gadget_Main_Panel,10,10,735,435)
  AddGadgetItem(#Gadget_Main_Panel,-1,"Tab 1")
  ListIconGadget(#Gadget_Main_List,10,10,710,395,"My Data",600,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect|#PB_ListIcon_AlwaysShowSelection)
  CloseGadgetList()

  For tmp=1 To 50
    AddGadgetItem(#Gadget_Main_List,-1,"This is a test "+Str(tmp))
  Next  
  SetWindowCallback(@WindowCallback())
  
  Repeat:Until WaitWindowEvent()=#PB_Event_CloseWindow
EndIf
Sure would be nice to have the option to override the fix in 6.02 and put the "bug" back to have thing function as before. :twisted:

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 6:25 pm
by mk-soft
It must be the parent of the ListIconGadget. In this case, not the PanelGadget but the Item from the PanelGadget.

Solution:

Code: Select all

;-TOP

; Comment : Module SetGadgetCallback (Windows Only)
; Author  : mk-soft
; Version : v0.04
; Created : 10.06.2018
; Updated : 22.05.2023
; Link    : https://www.purebasic.fr/english/viewtopic.php?f=12&t=70842
;
; Syntax Callback:
;           Procedure GadgetCB(hWnd,uMsg,wParam,lParam)
;             Select uMsg
;               ;TODO
;             EndSelect
;             ; Call previous gadget procedure
;             ProcedureReturn CallGadgetProc(hWnd,uMsg,wParam,lParam)
;           EndProcedure
;
; *****************************************************************************

DeclareModule GadgetCallback
  
  Declare SetGadgetCallback(Gadget, *lpNewFunc, Parent = #False) 
  Declare CallGadgetProc(hWnd, uMsg, wParam, lParam)
  
EndDeclareModule

Module GadgetCallback
  
  EnableExplicit
  
  ; ---------------------------------------------------------------------------
  
  Procedure SetGadgetCallback(Gadget, *lpNewFunc, Parent = #False)
    Protected hWnd, *lpPrevFunc
    
    hWnd = GadgetID(Gadget)
    If Parent
      hwnd = GetParent_(hwnd)
    EndIf
    *lpPrevFunc = GetProp_(hWnd, "PB_PrevFunc")
    ; Remove exists Callback
    If *lpPrevFunc
      SetWindowLongPtr_(hWnd, #GWL_WNDPROC, *lpPrevFunc)
      RemoveProp_(hWnd, "PB_PrevFunc")
    EndIf
    ; Set new Callback  
    If *lpNewFunc
      *lpPrevFunc = SetWindowLongPtr_(hWnd, #GWL_WNDPROC, *lpNewFunc)
      SetProp_(hWnd, "PB_PrevFunc", *lpPrevFunc)
      ProcedureReturn *lpPrevFunc
    EndIf
    ProcedureReturn 0
  EndProcedure
  
  ; ---------------------------------------------------------------------------
  
  Procedure CallGadgetProc(hWnd, uMsg, wParam, lParam)
    Protected result, *lpPrevFunc
    
    *lpPrevFunc = GetProp_(hWnd, "PB_PrevFunc")
    If *lpPrevFunc
      result = CallWindowProc_(*lpPrevFunc, hWnd, uMsg, wParam, lParam)
    EndIf
    ProcedureReturn result
  EndProcedure
EndModule

; *****************************************************************************


EnableExplicit

UseModule GadgetCallback

#Window_Main=1
#Gadget_Main_Panel=1
#Gadget_Main_Tab1=2
#Gadget_Main_List=3

Define hRowSelected,hRow

Procedure GadgetCallback(WindowID,Message,wParam,lParam)
  Shared hRowSelected,hRow
  Protected *LVCDHeader.NMLVCUSTOMDRAW
  
  Select Message
    Case #WM_NOTIFY 
      *LVCDHeader.NMLVCUSTOMDRAW=lParam           
      If *LVCDHeader\nmcd\hdr\code=#NM_CUSTOMDRAW
        If *LVCDHeader\nmcd\hdr\hwndFrom=GadgetID(#Gadget_Main_List)
          Select *LVCDHeader\nmcd\dwDrawStage 
            Case #CDDS_PREPAINT 
              ProcedureReturn #CDRF_NOTIFYITEMDRAW 

            Case #CDDS_ITEMPREPAINT 
              hRowSelected=#False 
              hRow=*LVCDHeader\nmcd\dwItemSpec   
              If GetGadgetItemState(#Gadget_Main_List,hRow) & #PB_ListIcon_Selected
                hRowSelected=#True
                *LVCDHeader\clrText=$FFFFFF
              Else
                *LVCDHeader\clrText=$000000
              EndIf
              
              If hRowSelected
                SetGadgetItemState(#Gadget_Main_List,hRow,0)
                *LVCDHeader\clrTextBk=$000000
                ProcedureReturn #CDRF_NEWFONT|#CDRF_NOTIFYPOSTPAINT
              Else                  
                If (hRow/2)*2=hRow
                  *LVCDHeader\clrTextBk=$DFFFE7
                Else
                  *LVCDHeader\clrTextBk=$F3FFF6                                        
                EndIf
                ProcedureReturn #CDRF_NEWFONT
              EndIf
              
            Case #CDDS_ITEMPOSTPAINT
              If hRowSelected
                SetGadgetItemState(#Gadget_Main_List,hRow,#PB_ListIcon_Selected)
              EndIf
              ProcedureReturn #CDRF_DODEFAULT

          EndSelect        
        EndIf
      EndIf    
    
  EndSelect  
  
  ProcedureReturn CallGadgetProc(WindowID,Message,wParam,lParam)
EndProcedure

Define tmp

If OpenWindow(#Window_Main,0,0,759,458,"Panel Test",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
  PanelGadget(#Gadget_Main_Panel,10,10,735,435)
  AddGadgetItem(#Gadget_Main_Panel,-1,"Tab 1")
  ListIconGadget(#Gadget_Main_List,10,10,710,395,"My Data",600,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect|#PB_ListIcon_AlwaysShowSelection)
  CloseGadgetList()

  For tmp=1 To 50
    AddGadgetItem(#Gadget_Main_List,-1,"This is a test "+Str(tmp))
  Next  
  
  SetGadgetCallback(#Gadget_Main_List, @GadgetCallback(), #True)
  
  Repeat:Until WaitWindowEvent()=#PB_Event_CloseWindow
EndIf

Re: #WM_NOTIFY in callback failure

Posted: Mon May 22, 2023 6:33 pm
by Paul
Thanks mk-soft, greatly appreciated !!

Re: #WM_NOTIFY in callback failure

Posted: Tue May 23, 2023 12:04 am
by BarryG
Okay, after sleeping on it, I've come to the conclusion that the time-tested and good-old method of using one single neat window callback to catch all non-PureBasic messages is now gone with 6.02. Sadly, I don't want to have to code multiple callbacks for multiple gadgets (I have hundreds) to work around this massive code-altering change, so I've decided 6.01 is the final PureBasic version for me. Thanks for trying to help anyway, guys, but it's way too much extra work and hassle to deal with for a 60000+ line app.

Afterthought: It also means I can't contribute code here anymore in a lot of cases, as I'll be using the old code method which won't run for users who are on 6.02 and higher. In fact, a lot of old code and historic tips on this forum are now broken because of this change. :( I ask that it goes back to the old way.

Re: #WM_NOTIFY in callback failure

Posted: Tue May 23, 2023 12:40 am
by mk-soft
It's not that extreme after all. You can also rewrite the WindowCallback on my example with SetGadgetCallback once and redirect all gadgets, even those that have the window as parent, to the same Routime.

Re: #WM_NOTIFY in callback failure

Posted: Tue May 23, 2023 1:09 am
by BarryG
It's not that simple. Your GadgetCallback() example is specific to ListIcons. I need to handle ANY gadget that needs #WM_NOTIFY triggered, but specifically as I mentioned: ListIcons, CalendarGadgets, and PanelGadgets. It's a LOT of re-work when I can just use 6.01 and forget about the hassle. Time is money.

Also, your SetGadgetCallback() is ProcedureReturning either 0 or CallWindowProc_(*lpPrevFunc, hWnd, uMsg, wParam, lParam), but my app needs to return 1 at times, so I can't use your code anyway. Thanks for posting it, though.

If anyone can show me a drop-in way to make ANY gadget get notified of #WM_NOTIFY, without having to code something for every gadget (I have hundreds), then I'm all ears. Even more so if it can just transfer that message to SetWindowCallback() instead so I can handle it there, like 6.01 does. But that still leaves the problem of lots of broken historic code on this forum now.