wxAuiNotebook, missing PageChange/Close event

BlitzMax Forums/Brucey's Modules/wxAuiNotebook, missing PageChange/Close event

Jim Teeuwen(Posted 2009) [#1]
Hello peeps.

I've been fiddling around with the wxAUI framework for a bit. Specifically with the wxAUINotebook widget (awesome stuff!)

There seems to be a problem though with missing events.
You can add/remove pages in the notebook. Adding pages fires off a wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED event. Closing pages does this too, except for the last page in the notebook. When you remove the last one, nothing fires.

Destroying pages, should fire wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, but this one seems to be ignored completely.

This missing last event is pretty critical unfortunately. In my current code, these events are used to enable/disable menuitems based on whether there's an active tab or not. The last event with disable them, but as it stands this never happens. There is a partial workaround; I use menuitems with shortcuts to remove tabs (Close, Close All, etc). These menuitem eventhandlers can do the updating of the menuitem states, but they will never be called if the user clicks the little X icon on each tab instead of using a menuitem.


Here is a proof of concept:

SuperStrict

Framework wx.wxApp

Import brl.basic
Import wx.wxFrame
Import wx.wxTextCtrl
Import wx.wxButton
Import wx.wxPanel 
Import wx.wxAUI

New MyApp.Run();
Type MyApp Extends wxApp
	Method OnInit:Int()
		Local frm:MyFrame = MyFrame(New MyFrame.Create(Null, wxID_ANY));
		frm.Show(True);
		Return True;
	End Method
End Type

Const BTN_ADD:Int = 0;
Const BTN_REM:Int = 1;

Type MyFrame Extends wxFrame
	Field tabs:wxAuiNotebook;
	
	Method OnInit:Int()
		Local panel:wxPanel = New wxPanel.Create(Self, -1);
		Local sizer:wxBoxSizer = New wxBoxSizer.Create(wxVERTICAL);
		
		'// add /remove buttons
		sizer.Add(New wxButton.Create(panel, BTN_ADD, "Add"), 0, wxEXPAND | wxALL, 3);
		sizer.Add(New wxButton.Create(panel, BTN_REM, "Remove"), 0, wxEXPAND | wxALL, 3);
		
		'// hook em up
		Self.Connect(BTN_ADD, wxEVT_COMMAND_BUTTON_CLICKED, Add_Clicked);
		Self.Connect(BTN_REM, wxEVT_COMMAND_BUTTON_CLICKED, Remove_Clicked);
	
		'// create tab control
		Self.tabs = New wxAuiNotebook.Create(panel, -1);
		
		'// hook page change/close events
		Self.ConnectAny(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, tabs_PageChanged);
		Self.ConnectAny(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, tabs_PageClosed);
		
		sizer.Add(Self.tabs, 1, wxEXPAND | wxALL, 3);
		panel.SetSizer(sizer);
	End Method
	
	Function Add_Clicked(event:wxEvent)
		Local frm:MyFrame = MyFrame (event.parent);
		Local txt:wxTextCtrl = New wxTextCtrl.Create(frm, -1);
		frm.tabs.AddPage(txt, "Tab " + frm.tabs.GetPageCount(), True);
	End Function
	Function Remove_Clicked(event:wxEvent)
		Local frm:MyFrame = MyFrame (event.Parent);
		If(frm.tabs.GetPageCount() = 0) Then
			Return;
		End If

		'// This here should trigger a PAGE_CLOSE event, but it does not.
		frm.tabs.DeletePage(frm.tabs.GetSelection());
	End Function
	Function tabs_PageChanged(event:wxEvent)
		Print("tabs_PageChanged!");
	End Function
	Function tabs_PageClosed(event:wxEvent)
		Print("tabs_PageClosed!");
	End Function
End Type




Jim Teeuwen(Posted 2009) [#2]
I just realized that the last PAGE_CHANGE event is never fired, because it is intended only to notify you of selection changes. When the last tab disappears, there is no new tab to switch to, so no event is fired. This means my assumption that this is a bug is False and the behaviour is as intended.

However, the PAGE_CLOSE event should still be fired, which it is not.


DavidDC(Posted 2009) [#3]
Clicking the X on each tab does call the PAGE_CLOSE event in XP? And as you say, if the user is pressing a button to close a tab, or using a menu item to close a tab - the you can use the button / menu event to acknowledge the close?

But maybe I haven't understood the problem correctly?

I've never used this control - but I see getPage returns a wxWindow. Perhaps you could hook into the window wxEVT_CLOSE_WINDOW event of each tab? Just a guess.


Jim Teeuwen(Posted 2009) [#4]
Mmm, PAGE_CLOSE is indeed triggered when clicking the X button. Now i just need to find a way to make it work with the menu items.

You are right about the pages being a wxWindow. I'll give your suggestion a try.

Edit: mm nope. Seems the wxEVT_CLOSE_WINDOW event is not fired either.\
Here's the revised code for reference.

	Function Add_Clicked(event:wxEvent)
		Local frm:MyFrame = MyFrame (event.parent);
		Local txt:wxTextCtrl = New wxTextCtrl.Create(frm, -1);
		frm.tabs.AddPage(txt, "Tab " + frm.tabs.GetPageCount(), True);
		
		'// Use GetSelection() because the id returned by AddPage does not seem to be valid.
		'// GetSelection will return the new page, because we set the 'selection' param to true
		'// in the AddPage() call. This means the new page is automatically made the active page.
		Local id:Int = frm.tabs.GetPage(frm.tabs.GetSelection()).GetId();
		frm.Connect(id, wxEVT_CLOSE_WINDOW, tabs_PageClosed);
	End Function


Note that I was not sure whether to use frm.Connect() or frm.tabs.Connect(), because the page is technically owned by the tab control, not the form. I tried the code with both options and neither worked though.


Edit 2: Aaaah lol. Well I can hook up the required event handlers by updating the form in the PAGE_CLOSE event handler in case the user presses the X, and separately in the menu handlers. This works fine, except that after you created multiple tabs, then close them 1 by 1, the GetPageCount() will not return the amount of pages AFTER the deletion, but always 1 more.

The function that disables menu items does so based on the amount of tabpages.. If there are 0, then disable the menuitems. Initially, upon program start, PageCount() = 0.. then for every oage you add it update correctly.. 1, 2, 3.

Then you close the first page, but instead of getting 2, you still get 3.. When all pages are gone, GetPageCount() will continue to be 1. This value is only updated after all the PAGE_CLOSE event handlers have been processed.


The fix for my problem is simple enough, though a bit odd.
Te function that does the menu item updates now takes an Int parameter that can be either 0 or 1. Only when the PAGE_CLOSE is triggered, will 1 be passed because of the GetPageCount() being incorrect. The function now determines if a menu sho;uld be enabled by doing: active = (GetPageCount() > intparameter);

This seems to solve the issue.


Brucey(Posted 2009) [#5]
However, the PAGE_CLOSE event should still be fired, which it is not.

This is interesting, because as far as I can tell from the code, PAGE_CLOSE/CLOSED should never be fired when you call DeletePage(), only when you hit the close button on the tab - which makes sense, since the event is really to know when a tab closes, and if you are deleting it yourself, you should know you are closing it ;-)

On Mac, no CLOSE/CLOSED events are raised when DeletePage() is called, which is what I'd expect. Now I can't remember if the same was true on Windows... argh! (it should be, since they share the same code!).

I'll be releasing a minor update to this mod with some missing events.

I'm also looking to move to 2.8.9, which adds some fixes and new things, but the code for this appears to be unchanged.


Artemis(Posted 2009) [#6]
If you want to have the event fired in case the button is clicked, you can fire it by yourself ?!