Notebook/Panel Layout Not Refreshed Consistently with Opened Dialog

I am running Ubuntu/Lubuntu Linux 22.04.1 x86_64 which has the following packages:

wxPython: python3-wxgtk4.0 (4.0.7)
wxWidgets: libwxgtk3.0-gtk3-0v5 (3.0.5.1)
Gtk: libgtk-3-0 (3.24.33)

Python/wxPython info:

$ python3
Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import wx
>>> wx.version()
'4.0.7 gtk3 (phoenix) wxWidgets 3.0.5'

My problem is that I cannot get a page (wx.Panel) of a wx.Notebook to refresh its layout consistently. The following is a sample app to demonstrate:

import wx

class Dialog(wx.Dialog):
	def __init__(self, parent):
		wx.Dialog.__init__(self, parent, wx.ID_ANY, "A Dialog Window",
				parent.GetPosition(), wx.Size(640, 480),
				wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)

		tabs = wx.Notebook(self)

		panel1 = wx.Panel(tabs, wx.ID_ANY)
		layout1 = wx.BoxSizer(wx.VERTICAL)
		layout1.AddStretchSpacer()
		layout1.Add(wx.StaticText(panel1, label="Centered"), 1,
				wx.ALIGN_CENTER)
		layout1.AddStretchSpacer()

		panel1.SetSizer(layout1)
		panel1.SetAutoLayout(True)
		panel1.Layout()

		panel2 = wx.Panel(tabs, wx.ID_ANY)
		layout2 = wx.BoxSizer(wx.VERTICAL)
		layout2.Add(wx.StaticText(panel2, label="Not Centered"), 1)

		panel2.SetSizer(layout2)
		panel2.SetAutoLayout(True)
		panel2.Layout()

		tabs.AddPage(panel1, "Page 1")
		tabs.AddPage(panel2, "Page 2")

		self.Refresh()
		self.Update()

class Window(wx.Frame):
	def __init__(self):
		wx.Frame.__init__(self, None, wx.ID_ANY, "Test", wx.Point(50, 50),
				wx.Size(200, 200))

		btn = wx.Button(self, wx.ID_ANY, "PUSH ME")
		btn.Bind(wx.EVT_BUTTON, self.onButton)

	def onButton(self, evt):
		dia = Dialog(self)
		dia.ShowModal()
		# ~ dia.Destroy()

app = wx.App()
frame = Window()
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()

When the button is pressed & the dialog is opened, the text on the first page should be centered. However, this generally isn’t the case initially. Usually the dialog will open without the notebook’s children being positioned & sized correctly. It appears that the panel isn’t being expanded to fill the entire page. Like in the following image.

image

If I repeatedly close & reopen the dialog it will eventually be laid out correctly, as in the following image.

image

Once it has opened with the correct layout, it will do the same when opened subsequently. It is also possible to force the layout to refresh by resizing the dialog window.

I am wondering if this is a problem with Gtk itself. When the dialog opens with the correct layout the system prints this message (error?):

gtk_box_gadget_distribute: assertion 'size >= 0' failed in GtkNotebook

I don’t know what it means, but it sounds like Gtk is having issues detecting space for the widgets. And it is weird that the message displays when the layout is correct. The problem sounds similar to the issues described in Frame doesn't refresh its content & self.Refresh() doesn't do anything. And I have tried using Refresh() & Update() on the dialog, the notebook, & the panel without any change in behavior. I have also tried adding wx.GetApp().Yield() before the call to ShowModal().

Is it possible to get it to work every time the dialog is opened or is this a Gtk platform issue that wxPython isn’t able to circumvent?

I have also asked about this on Stackoverflow ( python - wxPython/wxGtk Platform Issue: Cannot Force Refresh/Update on Opening Modal Dialog Panel - Stack Overflow ). But at the time of posting this topic, have not received any suggestions.

Edit: I forgot to mention that making the dialog an attribute of the wx.Frame class solves the problem. But I would like the dialog to be created in the button event handler.

	...
		self.dia = Dialog(self)

	def onButton(self, evt):
		self.dia.ShowModal()
	...

Edit: If I remove the call to panel1.Layout() the text is fully displayed but still not centered. If I use panel1.SetSizerAndFit some space is allocated above for the spacer, but text is still off center.

	...
		#panel1.SetSizer(layout1)
		#panel1.SetAutoLayout(True)
		#panel1.Layout()
		panel1.SetSizerAndFit(layout1)
	...

image

Perhaps related, the dialog window does not always respect the pos parameter when it is constructed.

Edit: I created a video to demonstrate the behavior: https://youtu.be/WKcFXOVhzAU

It seems to be fixed in later versions of wxPython, at least on my machine. I tested with 4.1.1 and 4.2.0.

If I were to take a guess I would say that the initial size event (where the auto layout will happen) is happening before you’ve added the widget to the panel, although your calls to Layout would be expected to take care of that. Try doing wx.CallAfter(panel1.Layout) instead, which should delay the Layout until ShowModal starts its nested event loop.

Also, your

		self.Refresh()
		self.Update()

are not necessary for resolving layout issues. They only deal with sending and handling paint events.

I get the same result with wx.CallAfter. Not sure when Ubuntu will upgrade. Debian doesn’t have a newer version in their unstable release either. Won’t be worth distributing my app until they do as it is designed for Debian/Ubuntu.

Thanks for replying. I might try a PPA or a self-build just to see if it works for me.

Edit: … or a Pypi package.

It works correctly with 4.1.1 (PyPI).