Search code examples
user-interfacewxpythonwxwidgets

expand wxWrapSizer on secondary direction when adding new items


I have a panel that is a list of buttons. The number of buttons changes during runtime (due to user action elsewhere). I use a wxWrapSizer to manage these button since I want the height of this panel to remain the same and create a second column of buttons when it runs out of vertical space. The height is managed by the parent sizer based on the other widgets height. This almost works fine but the second column of buttons does not appear until after the window is manually resized.

I have created a minimal example to reproduce the issue:

import wx

start_buttons = 5

class ButtonsPanel(wx.Panel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.SetSizer(wx.WrapSizer(wx.VERTICAL))
        for i in range(start_buttons):
            self.add_button()

    def add_button(self):
        self.GetSizer().Add(wx.Button(self, label='foo'),
                            wx.SizerFlags().Expand())


class MyPanel(wx.Panel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # hardcoded size for sake of example only
        add_button = wx.Button(self, label="add", size=(80, 250))
        add_button.Bind(wx.EVT_BUTTON, self.OnAddButton)

        self.buttons_panel = ButtonsPanel(self)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(add_button)
        sizer.Add(self.buttons_panel, wx.SizerFlags().Expand())
        self.SetSizer(sizer)

    def OnAddButton(self, evt):
        self.buttons_panel.add_button()
        self.buttons_panel.Layout()


class MyFrame(wx.Frame):
    def __init__(self, *args):
        super().__init__(*args)
        panel = MyPanel(self)


app = wx.App()
frame = MyFrame(None)
frame.Show()

app.MainLoop()

Clicking the large "Add" button will add new "foo" buttons but then it stops once it reaches the bottom of the frame. Manually resizing the frame will make the hidden second column appear.

screenshot of the example running


Solution

  • Perform a Layout() of the main panel's sizer rather than the buttons panel sizer.

    As the buttons panel's sizer "lives" inside the main panel's sizer, this is the one that needs to be recalculated, as it will recalculate it's children.

    def OnAddButton(self, evt):
        self.buttons_panel.add_button()
        #self.buttons_panel.Layout()
        self.Layout()
    

    Edit:
    for more complex setups you may need to note the parentor grandparent and update from within the buttonpanel i.e.

    import wx
    
    start_buttons = 5
    
    class ButtonsPanel(wx.Panel):
        def __init__(self, parent, *args, **kwargs):
            super().__init__(parent, *args, **kwargs)
            self.SetSizer(wx.WrapSizer(wx.VERTICAL))
            self.parent = parent
            for i in range(start_buttons):
                self.add_button()
    
        def add_button(self):
            self.GetSizer().Add(wx.Button(self, label='foo'),
                                wx.SizerFlags().Expand())
            self.parent.Layout()
    
    class MyPanel(wx.Panel):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            # hardcoded size for sake of example only
            add_button = wx.Button(self, label="add", size=(80, 250))
            add_button.Bind(wx.EVT_BUTTON, self.OnAddButton)
    
            self.buttons_panel = ButtonsPanel(self)
    
            sizer = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(add_button)
            sizer.Add(self.buttons_panel, wx.SizerFlags().Expand())
            self.SetSizer(sizer)
    
        def OnAddButton(self, evt):
            self.buttons_panel.add_button()
            #self.buttons_panel.Layout()
    
    class MyFrame(wx.Frame):
        def __init__(self, *args):
            super().__init__(*args)
            panel = MyPanel(self)
    
    
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    
    app.MainLoop()
    

    If it gets truly hellish, you could use pubsub to fire a Layout in the right place.