Find all nested SizerItems within a Frame in wxPython

In wxPython, I am trying to adjust the border of items within a form on startup programatically in order to support higher DPI displays a bit better. I want to do is loop through each item in the frame (i.e. each row as shown in the editor) and double the "border" attribute if it has one. This mostly works great. However, some sizers I'm unable to find programmatically, specifically those which are nested inside other sizers.

I've recreated this on a simplified form: Example project layout

I have set the border of each item to unique values so that I know which ones have been hit.

  • panel1: border of 1
  • hiddenSizer: border of 2
  • bSizer261: border of 3
  • bSizer27: border of 4
  • bSizer28: border of 5
  • bSizer29: border of 6
  • m_staticText29: border of 7

My code to (attempt to) find all SizerItems is the following:

import wx

def walk_children(item):
    for value in item.GetChildren():
        yield value
        for subvalue in walk_children(value):
            yield subvalue

def adjust_border(frame : wx.Window):
    for x in walk_children(frame):
        print()
        sizer = x.GetContainingSizer()
        parents = [x]
        while parents[-1].GetParent():
            parents.append(parents[-1].GetParent())
        print(x, sizer)
        print(f"parents: {parents}")
        if sizer:
            for x in sizer.GetChildren():
                print(x, x.GetBorder())
                x.SetBorder(x.GetBorder() * 2)


class MainFrameBase ( wx.Frame ):

    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"My Window", pos = wx.DefaultPosition, size = wx.Size( 318,359 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
        self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) )
        bSizer1 = wx.BoxSizer( wx.HORIZONTAL )
        self.panel1 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer23 = wx.BoxSizer( wx.VERTICAL )
        hiddenSizer = wx.StaticBoxSizer( wx.StaticBox( self.panel1, wx.ID_ANY, u"Top Panel" ), wx.VERTICAL )
        bSizer261 = wx.BoxSizer( wx.VERTICAL )
        bSizer27 = wx.BoxSizer( wx.VERTICAL )
        bSizer28 = wx.BoxSizer( wx.VERTICAL )
        bSizer29 = wx.BoxSizer( wx.VERTICAL )
        self.m_staticText29 = wx.StaticText( hiddenSizer.GetStaticBox(), wx.ID_ANY, u"MyLabel", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText29.Wrap( -1 )
        bSizer29.Add( self.m_staticText29, 0, wx.ALL, 7 )
        bSizer28.Add( bSizer29, 1, wx.ALL|wx.EXPAND, 6 )
        bSizer27.Add( bSizer28, 1, wx.ALL|wx.EXPAND, 5 )
        bSizer261.Add( bSizer27, 1, wx.ALL|wx.EXPAND, 4 )
        hiddenSizer.Add( bSizer261, 1, wx.ALL|wx.EXPAND, 3 )
        bSizer23.Add( hiddenSizer, 1, wx.ALL|wx.EXPAND, 2 )
        self.panel1.SetSizer( bSizer23 )
        self.panel1.Layout()
        bSizer23.Fit( self.panel1 )
        bSizer1.Add( self.panel1, 1, wx.EXPAND |wx.ALL, 1 )
        self.SetSizer( bSizer1 )
        self.Layout()
        self.Centre( wx.BOTH )

    def __del__( self ):
        pass


class MainFrame(MainFrameBase):

    def __init__(self, parent):
        # initialize
        super().__init__(parent)
        self.Layout()
        adjust_border(self)


def run_gui():
    # create the window
    app = wx.App()
    ex = MainFrame(None)
    ex.Show()
    app.MainLoop()


if __name__ == "__main__":
    run_gui()

The output is this which indicates only 3/7 of them haved been found (those with borders of 1, 3, and 7:

<wx._core.Panel object at 0x00000133C3DD91F8> <wx._core.BoxSizer object at 0x00000133C3DD0DC8>
parents: [<wx._core.Panel object at 0x00000133C3DD91F8>, <__main__.MainFrame object at 0x00000133C3DD0D38>]
<wx._core.SizerItem object at 0x00000133C3FA2708> 1

<wx._core.StaticBox object at 0x00000133C3DD5E58> <wx._core.StaticBoxSizer object at 0x00000133C3DD5EE8>
parents: [<wx._core.StaticBox object at 0x00000133C3DD5E58>, <wx._core.Panel object at 0x00000133C3DD91F8>, <__main__.MainFrame object at 0x00000133C3DD0D38>]
<wx._core.SizerItem object at 0x00000133C3FA2708> 3

<wx._core.StaticText object at 0x00000133C3FA24C8> <wx._core.BoxSizer object at 0x00000133C3FA2438>
parents: [<wx._core.StaticText object at 0x00000133C3FA24C8>, <wx._core.StaticBox object at 0x00000133C3DD5E58>, <wx._core.Panel object at 0x00000133C3DD91F8>, <__main__.MainFrame object at 0x00000133C3DD0D38>]
<wx._core.SizerItem object at 0x00000133C3FA2708> 7

How do I loop through each SizerItem in a frame programmatically without skipping entries?

There are similar questions like this, but the answers do not provide nested SizerItems. Generate List of All sizerItems Nested in wxPython Sizers

2 answers

  • answered 2022-05-03 06:10 Psionman

    You need to process the sizers recursively. The function adjust_border takes the top level sizer and works down through all of its children recursively

    Your code also seems to contain a panel definition in the frame. I have created a separate class for the panel.

    Each frame/panel should only have one main sizer (see the setSizer function) and so you need to call adjust_border for each and every top level sizer - in this case once for the frame and once for the panel

        import wx
    
    
    class MainFrame(wx.Frame):
        def __init__(self, parent):
            wx.Frame.__init__(self, parent, title=u"My Window", size=wx.Size(318, 359))
            self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
            self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
            self.panel1 = MainPanel(self)
    
            bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
            bSizer1.Add(self.panel1, 1, wx.EXPAND | wx.ALL, 1)
            self.SetSizer(bSizer1)
            self.Layout()
            self.Centre(wx.BOTH)
            self.adjust_border(bSizer1)
            self.Layout()
    
        def adjust_border(self, sizer):
            for child in sizer.GetChildren():
                print(child.GetBorder())
                if child.IsSizer():
                    self.adjust_border(child.GetSizer())
                child.SetBorder(child.GetBorder() * 2)
    
    
    class MainPanel(wx.Panel):
        def __init__(self, parent, *args, **kwargs):
            super().__init__(parent, *args, **kwargs)
            bSizer23 = wx.BoxSizer(wx.VERTICAL)
            bSizer261 = wx.BoxSizer(wx.VERTICAL)
            bSizer27 = wx.BoxSizer(wx.VERTICAL)
            bSizer28 = wx.BoxSizer(wx.VERTICAL)
            bSizer29 = wx.BoxSizer(wx.VERTICAL)
    
            hidden_box = wx.StaticBox(self, wx.ID_ANY, u"Top Panel")
            hiddenSizer = wx.StaticBoxSizer(hidden_box, wx.VERTICAL)
            self.m_staticText29 = self._get_static_box(hiddenSizer)
    
            bSizer29.Add(self.m_staticText29, 0, wx.ALL, 7)
            bSizer28.Add(bSizer29, 1, wx.ALL | wx.EXPAND, 6)
            bSizer27.Add(bSizer28, 1, wx.ALL | wx.EXPAND, 5)
            bSizer261.Add(bSizer27, 1, wx.ALL | wx.EXPAND, 4)
            hiddenSizer.Add(bSizer261, 1, wx.ALL | wx.EXPAND, 3)
            bSizer23.Add(hiddenSizer, 1, wx.ALL | wx.EXPAND, 2)
    
            self.SetSizer(bSizer23)
            self.Layout()
            bSizer23.Fit(self)
            parent.adjust_border(bSizer23)
    
        @staticmethod
        def _get_static_box(sizer):
            static_text = wx.StaticText(sizer.GetStaticBox(), wx.ID_ANY, u"MyLabel", wx.DefaultPosition, wx.DefaultSize, 0)
            static_text.Wrap(-1)
            return static_text
    
    
    if __name__ == "__main__":
        # create the window
        app = wx.App()
        ex = MainFrame(None)
        ex.Show()
        app.MainLoop()
    

  • answered 2022-05-03 16:24 devtk

    This code will find items using GetSizer and GetChildren functions recursively, which works for the example given. Unclear if it will work on more complex examples.

    def find_all_sizer_items(frame):
        """Return a set of all SizerItems within a wx.Frame."""
        all_items = set()
        new_items = [frame]
        while new_items:
            this_item = new_items.pop()
            all_items.add(this_item)
            test_items = []
            if hasattr(this_item, "GetSizer"):
                test_items.append(this_item.GetSizer())
            if hasattr(this_item, "GetChildren"):
                test_items.extend(this_item.GetChildren())
            for x in test_items:
                if x is None or x in all_items or x in new_items:
                    continue
                print(x)
                all_items.add(x)
                new_items.append(x)
        return [x for x in all_items if isinstance(x, wx.SizerItem)]
    

    I don't love using hasattr here, but it is effective. Presumably there would be some better check but someone with more wxPython knowledge would need to weigh in there.

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum