Menu lifecycle

Just starting out? Need help? Post your questions and find answers here.
tua
User
User
Posts: 68
Joined: Sun Jul 23, 2023 8:49 pm
Location: BC, Canada

Menu lifecycle

Post by tua »

From the help on CloseWindow():

Note: The following items created for a window are also automatically freed when the window is closed: Gadgets, Keyboard shortcuts, Menus, StatusBars, Timers, Toolbars and bound events (with BindEvent()).

I thought I understood this, but maybe not...?

Run my issue-demo program below, click on a main menu item and notice that nothing happens (as expected, since no event is hooked up to the main menu as the relevant line is commented out).

Now open the Stuff window, invoke its popupmenu and see it generate debug messages as expected upon menuitems being clicked. Close the Stuff window and again click on any of the main menu items. It now invokes the popupmenu from the Stuff window...despite the main menu not being hooked up to an event and despite the Stuff window having been closed.

Can someone enlighten me?

Code: Select all

DeclareModule Stuff
  Declare StuffMain()
EndDeclareModule 

Module Stuff
  EnableExplicit
  
  Declare CreatePopupMenuStuff()
  Declare PopupMenuStuffClick()
  Declare ShowPopupMenuStuff()
  Declare WindowClose()
    
  Global StuffWindow, PopupMenuStuff
  
  Procedure CreatePopupMenuStuff()
    PopupMenuStuff = CreatePopupMenu(#PB_Any) 
    If PopupMenuStuff
      MenuItem(0, "Stuff 0") 
      MenuItem(1, "Stuff 1")
      MenuItem(2, "Stuff 2")
    EndIf  
  EndProcedure
  
  Procedure ShowPopupmenuStuff()
    If IsMenu(PopupMenuStuff)
      DisplayPopupMenu(PopupMenuStuff, WindowID(StuffWindow))
    EndIf  
  EndProcedure  
  
  Procedure PopupMenuStuffClick()
    Select EventMenu()  
      Case 0: Debug "Menuitem 0"
      Case 1: Debug "Menuitem 1"
      Case 2: Debug "Menuitem 2"  
    EndSelect    
  EndProcedure
  
  Procedure WindowClose()
    CloseWindow(StuffWindow)
  EndProcedure  
  
  Procedure StuffMain()
    StuffWindow = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 300, 300, "Stuff window", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    If StuffWindow
      CreatePopupMenuStuff()
      Protected button = ButtonGadget(#PB_Any, 100, 100, 60, 25, "Menu")
      ;
      BindEvent(#PB_Event_CloseWindow, @WindowClose(), StuffWindow) 
      BindEvent(#PB_Event_Menu, @PopupMenuStuffClick()) 
      ;
      BindGadgetEvent(button, @ShowPopupmenuStuff(), #PB_EventType_LeftClick)
    EndIf  
  EndProcedure
      
EndModule 

; ----------------------------------------------------------------------------------------------------------------------------

DeclareModule Demo
  ;
EndDeclareModule

Module Demo
  
  Declare WindowClose()
  Declare DisplayStuffWindow()
  Declare CreateMainMenu()
  Declare MainMenuClick()
    
  Global MainWindow, MainMenu
  
  Procedure WindowClose()
    CloseWindow(MainWindow)
  EndProcedure  
  
  Procedure DisplayStuffWindow()  
    Stuff::StuffMain()
  EndProcedure  
  
  Procedure CreateMainMenu()
    MainMenu = CreateMenu(#PB_Any, WindowID(MainWindow))
    If MainMenu
      MenuTitle("Main Menu")
      MenuItem(0, "Main Menuitem 0")
      MenuItem(1, "Main Menuitem 1")
      MenuItem(2, "Main Menuitem 2")
    EndIf
  EndProcedure
  
  Procedure MainMenuClick()
    Select EventMenu()  
      Case 0: Debug "Main Menuitem 0"
      Case 1: Debug "Main Menuitem 1"
      Case 2: Debug "Main Menuitem 2"  
    EndSelect   
  EndProcedure
  
  Procedure DemoMain()
    MainWindow = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 400, 400, "Demo window", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    If MainWindow
      CreateMainMenu()
      Protected button = ButtonGadget(#PB_Any, 100, 100, 120, 25, "Open Stuff window")
      ;
      BindEvent(#PB_Event_CloseWindow, @WindowClose(), MainWindow) 
      ;;; BindEvent(#PB_Event_Menu, @MainMenuClick())                           ; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
      ;
      BindGadgetEvent(button, @DisplayStuffWindow(), #PB_EventType_LeftClick)
    EndIf  
  EndProcedure
  
  DemoMain()
  
  Repeat: Until WaitWindowEvent() = #PB_Event_CloseWindow
  
EndModule 
SMaag
Enthusiast
Enthusiast
Posts: 324
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: Menu lifecycle

Post by SMaag »

I did a small check of your Programm.

1. You have to use individual MenuID in each Window. Using ID 0,1,2 twice is not a good idea

2. The Event handling Loop
you have to create exactly one Event handling Loop for your Programm.
In this Loop you have to seperate the Events of each window with a selct or if Statement.
SMaag
Enthusiast
Enthusiast
Posts: 324
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: Menu lifecycle

Post by SMaag »

now I modified your code, so it Looks like running without crashing.

- same MenuItemID in different Menus is possible!

your errors:
1. Creating Stuff window more times
2. When Closing DemoWindow, you must give a Message to the Main Loop to leave.
This is not possible with a WindowClose Message, because of BindEvent, this will not be processed in the MainLoop
3. You have to bind the Menu Callback to the right Window, otherwise you bind all MenuEvents to the same CallbackProc

Code: Select all

DeclareModule Stuff
  Global hPB
  Declare Create()
EndDeclareModule 

Module Stuff
  EnableExplicit
  
;   Declare CreatePopupMenuStuff()
;   Declare PopupMenuStuffClick()
;   Declare ShowPopupMenuStuff()
;   Declare WindowClose()
    
  Global PopupMenuStuff
  
  Procedure CreatePopupMenuStuff()
    PopupMenuStuff = CreatePopupMenu(#PB_Any) 
    If PopupMenuStuff
      MenuItem(0, "Stuff 0") 
      MenuItem(1, "Stuff 1")
      MenuItem(2, "Stuff 2")
    EndIf  
  EndProcedure
  
  Procedure ShowPopupmenuStuff()
    If IsMenu(PopupMenuStuff)
      DisplayPopupMenu(PopupMenuStuff, WindowID(hPB))
    EndIf  
  EndProcedure  
  
  Procedure PopupMenuStuffClick()
    Select EventMenu()  
      Case 0: Debug "Menuitem 0"
      Case 1: Debug "Menuitem 1"
      Case 2: Debug "Menuitem 2"  
    EndSelect    
  EndProcedure
  
  Procedure WindowClose()
    CloseWindow(hPB)
  EndProcedure  
  
  Procedure Create()
    hPB = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 300, 300, "Stuff window", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    If hPB
      CreatePopupMenuStuff()
      Protected button = ButtonGadget(#PB_Any, 100, 100, 60, 25, "Menu")
      ;
      BindEvent(#PB_Event_CloseWindow, @WindowClose(), hPB) 
      BindEvent(#PB_Event_Menu, @PopupMenuStuffClick(), hPB) 
      ;
      BindGadgetEvent(button, @ShowPopupmenuStuff(), #PB_EventType_LeftClick)
    EndIf  
  EndProcedure
      
EndModule 

; ----------------------------------------------------------------------------------------------------------------------------

DeclareModule Demo
  Global hPB, Quitt
  Declare Create()
  ; Declare Destroy()
EndDeclareModule

Module Demo
  
  EnableExplicit
  
;   Declare WindowClose()
;   Declare DisplayStuffWindow()
;   Declare CreateMainMenu()
;   Declare MainMenuClick()
    
  Global MainMenu
  
  Procedure Destroy()
    Quitt = #True
    CloseWindow(hPB)
  EndProcedure  
  
  Procedure EventProcCallback()  
    If Not Stuff::hPB
      Stuff::Create()
    EndIf
    
  EndProcedure  
  
  Procedure CreateMainMenu()
    MainMenu = CreateMenu(#PB_Any, WindowID(hPB))
    If MainMenu
      MenuTitle("Main Menu")
      MenuItem(0, "Main Menuitem 0")
      MenuItem(1, "Main Menuitem 1")
      MenuItem(2, "Main Menuitem 2")
    EndIf
  EndProcedure
  
  Procedure MainMenuClick()
    Select EventMenu()  
      Case 0: Debug "Main Menuitem 0"
      Case 1: Debug "Main Menuitem 1"
      Case 2: Debug "Main Menuitem 2"  
    EndSelect   
  EndProcedure
  
  Procedure Create()
    hPB = OpenWindow(#PB_Any, #PB_Ignore, #PB_Ignore, 400, 400, "Demo window", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    If hPB
      Debug "Demo Window Create"
      CreateMainMenu()
      Protected button = ButtonGadget(#PB_Any, 100, 100, 120, 25, "Open Stuff window")
      ;
      BindEvent(#PB_Event_CloseWindow, @Destroy(), hPB) 
      BindEvent(#PB_Event_Menu, @MainMenuClick(), hPB)                           ; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
      ;
      BindGadgetEvent(button, @EventProcCallback(), #PB_EventType_LeftClick)
    EndIf  
  EndProcedure
    
EndModule 


Demo::Create()
Define evt

Repeat
  evt = WaitWindowEvent()
Until Demo::Quitt


tua
User
User
Posts: 68
Joined: Sun Jul 23, 2023 8:49 pm
Location: BC, Canada

Re: Menu lifecycle

Post by tua »

Gruß nach Bayern!

Thanks - I am well aware of working around this by doing what you suggest. I do believe you are missing multiple points:

1) As I have quoted in my initial post, CloseWindow() is supposed to free menus and bound events - if that were true, how could the bound event from the Stuff window be invoked after it's freed?

2) Another point is that modules/namespaces are advertised at isolating things from each other, supposedly enabling me to do exactly what I am doing here:

Not having to worry about naming and ids in module bodies are long as they are not made public in the DeclareModule section. I don't want to nor should I have to avoid Bind(Gadget)Events to manually fiddle events apart by WindowID. I don't want a namespace system, which mostly works as expected, but then does weird stuff like sharing event ids across modules without me telling it to...

I have seen a few members of this forum suggest to completely ditch modules and to prefix pretty much everything with window names or such, but that to me is an abomination, like suggesting replacing flush toilets with outhouses - a fallback into last century.

I really like PureBasic otherwise, and I want to continue liking it, but things like this make that really hard... If I have to avoid namespaces for not being able to trust them, I cannot use PB for large projects which is a shame.
#NULL
Addict
Addict
Posts: 1499
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: Menu lifecycle

Post by #NULL »

Code: Select all

      BindEvent(#PB_Event_CloseWindow, @WindowClose(), StuffWindow) 
      BindEvent(#PB_Event_Menu, @PopupMenuStuffClick()) 
You didn't specify the StuffWindow for the menu bind, like you did for the close bind for example. That means from that point on, any menu event will trigger your procedure, including menu events from the first window.
1) As I have quoted in my initial post, CloseWindow() is supposed to free menus and bound events - if that were true, how could the bound event from the Stuff window be invoked after it's freed?
The window and menu are being freed like you said, but there is no "bound event from the Stuff window be invoked". it is menu events from the first/main window that are triggering your procedure, since you bound any/all menu events to your procedure due to not specifying the window.
Fred
Administrator
Administrator
Posts: 18199
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Menu lifecycle

Post by Fred »

As #Null said, you missed a parameter to specify the window, I don't see anything wrong here either with BindEvent() or modules...
tua
User
User
Posts: 68
Joined: Sun Jul 23, 2023 8:49 pm
Location: BC, Canada

Re: Menu lifecycle

Post by tua »

What a relief! Thanks so much.

I'd much rather be the fool who forgot a a crucial parameter than turn my back on PB for bigger projects! I was getting quite a bit worried after 4 days, over 60 reads and not a single reply until SMaag this morning ...
SMaag
Enthusiast
Enthusiast
Posts: 324
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: Menu lifecycle

Post by SMaag »

As Fred said, there is no big error unless the missing Parameter. But I suggest to change a Little bit the software architecture.


Try your orginal posted code. There you can open your stuff window multiple times. This will cause a lot of problemes, because multiinstanzing a window in that way is not possible. You have to check first wheater the Window is alive or not!
If it is alive (do not create it, just show it)

But using modules is the way to sepereate the namespaces. You do not have to prefix everything like in earlier PureBasic versions without modules. The pre fixing comes from that times. You are on the right way with the modules.

For me the better architectural way is to standadize the "Interface" for Windows/Forms.

Give every window a create, destroy, show, hide function (show and hide might be one function show(True/False)
and a GetState(). This makes it easier to switch later to real Interfaces and multiinstanz windows.
PureBasic don't has native support for this, it will be a manual work. But multiinstanzing a Window is an other thing.
Mostly it is not needed! So the pseudo Interface with the same function names in different modules is the perfect way to do.

frmMain::create()
frmMain::destroy()

I do not have a ready example here! Because of that I can only shwo the way to do!

Code: Select all

Module frmMain

Declare Create()
Declare Destroy()
Declare GetState()  ; this Returns the actual state of the window, like #wnd_created, #wnd_hidden or like that
.
.
EndModule 

Module frmStuff

Declare Create()
Declare Destroy()
Declare GetState()
.
.
.
EndModule 

[/code
Post Reply