Make Panel and SizedPanel play together

Does anyone know how to make wx.Panel and wx.lib.sized_controls.SizedPanel work together.

The problem is that some of the components in wxPython are based on wx.Panel. When I try to use them together, I get the following error

File "<redacted>/pyenv-3.12.4/lib/python3.12/site-packages/wx/lib/sized_controls.py", line 183, in SetSizerProp
#     flag = item.GetFlag()
# AttributeError: 'NoneType' object has no attribute 'GetFlag'
# OnInit returned false, exiting...

A use case for me is that I want to simplify building pages. WizardPageSimple is a wx.Panel. Thus, if build a SizedPanel parented by WizardPageSimple I get the above.

I know why, because I should be using all sized containers. I attached a small sample app that illustrates this.

I wonder if someone does not have a clever solution.

PanelContainingSizedPanel.py (1.9 KB)

on your snippet I get the error

Traceback (most recent call last):
File “C:\Users\Me\Documents\Programming\Python\Mylib\experimental\PanelContainingSizedPanel.py”, line 31, in OnInit
self._outerFrame = Frame(parent=None, id=ID_ANY, size=(WINDOW_WIDTH, WINDOW_HEIGHT), style=frameStyle, title=title)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
wx._core.wxAssertionError: C++ assertion ““Assert failure”” failed at …..\src\msw\toplevel.cpp(226) in wxTopLevelWindowMSW::MSWGetParent(): wxFRAME_FLOAT_ON_PARENT but no parent?
OnInit returned false, exiting…

the return code of ‘PanelContainingSizedPanel.py’ is 1

Press any key to continue . . . :face_with_raised_eyebrow:

I should have noted that I am running

wxPython 4.2.2
Python 3.12.4
Mac OS Sequoia 15.0
on a Mac book Pro M1

Ok I reposted the snippet by changing this line:

        frameStyle:     int        = DEFAULT_FRAME_STYLE | FRAME_FLOAT_ON_PARENT

to this

        frameStyle:     int        = DEFAULT_FRAME_STYLE

And attached the code again

PanelContainingSizedPanel.py (1.9 KB)

I never heard of SizedPanel before.

“Controls added to it will automatically be added to its sizer.”

Is this the only difference? Then I would suggest to avoid it. It reminds me of the “easy to use wrapper around wxPython” that many of us have created at some point and abandoned at another.

If you want to make your life easier, then many dialogs and windows can easily be created using a GUI builder like wxGlade.

I don’t know much about sized controls, apart from reading the documentation.

It seems to me that you should only call a control’s SetSizerProps() method if that control is being laid out by a sized control.

In your example, sizedPanel is a SizedPanel object, but it is not being laid out by another sized control, so you shouldn’t call its SetSizerProps() method. Instead, it should be added to boxSizer.

However, if you needed to set the sizer properties of any of its children, then you should call the children’s SetSizerProps() methods.

I modified your example along these lines and it appears to run OK using wxPython 4.2.2 gtk3 (phoenix) wxWidgets 3.2.6 + Python 3.12.3 + Linux Mint 22

from typing import cast

import wx
from wx import App
from wx import DEFAULT_FRAME_STYLE
from wx import FRAME_FLOAT_ON_PARENT
from wx import Frame
from wx import ID_ANY
from wx import Panel
from wx import StaticText

from wx.lib.agw.buttonpanel import BoxSizer

from wx.lib.sized_controls import SizedFrame
from wx.lib.sized_controls import SizedPanel

WINDOW_WIDTH:  int = 400
WINDOW_HEIGHT: int = 200


class SizedPanelApp(App):

    def __init__(self):
        super().__init__()
        self._outerFrame: SizedFrame = cast(SizedFrame, None)

    def OnInit(self) -> bool:
        title:          str        = 'Demo Sized Panel in Panel'
        frameStyle:     int        = DEFAULT_FRAME_STYLE

        self._outerFrame = Frame(parent=None, id=ID_ANY, size=(WINDOW_WIDTH, WINDOW_HEIGHT), style=frameStyle, title=title)

        # This simulates a wizard page (which is a panel)
        mainPanel: Panel = Panel(parent=self._outerFrame)
        boxSizer:  BoxSizer = BoxSizer()
        mainPanel.SetSizer(boxSizer)

        # This simulate a component that is a sized panel
        sizedPanel: SizedPanel = SizedPanel(parent=mainPanel)       # This is problematic
        sizedPanel.SetSizerType('vertical')

        boxSizer.Add(sizedPanel, 1, wx.EXPAND, 0)

        # noinspection PyUnusedLocal
        topLabel:    StaticText = StaticText(sizedPanel, ID_ANY, 'Top Label')
        # noinspection PyUnusedLocal
        bottomLabel: StaticText = StaticText(sizedPanel, ID_ANY, 'Bottom Label')

        self._outerFrame.Show(True)

        return True


testApp = SizedPanelApp()

testApp.MainLoop()

Having said that, I still prefer to use wxGlade to layout sizers directly, rather than use sized controls.

I’ve realised that what I posted above is not correct.

The critical thing is that a control must be added to a sizer before its SetSizerProps() method is called. That includes being added to a sizer explicitly, or by making it a child of a SizedPanel which automatically adds it to a sizer.

So in the modified example in my previous post, instead of:

        boxSizer.Add(sizedPanel, 1, wx.EXPAND, 0)

you could do:

        boxSizer.Add(sizedPanel)
        sizedPanel.SetSizerProps(expand=True)

Note: when you import the wx.lib.sized_controls module, it monkey patches several wxPython classes. This includes adding a number of methods to the wx.Window class (and thus any classes derived from it). One of those methods is SetSizerProps(). However, if a control of any of those classes has not been added to a sizer, it will raise an AttributeError exception if you call its SetSizerProps() method, as you got in your original post.

Ok, I get it now. This was a great help. Have to write this down in my personal notes