Search code examples
wxpythonwxwidgets

wxPython: layout issues using wx.CollapsiblePane with dynamic content


I'm trying to use a wx.CollapsiblePane to contain a list of steps, which the user can add to (and later delete). I've successfully got all of it working except that when a new step is added, the CollapsiblePane doesn't expand to fit the new line until it is manually collapsed and re-expanded. Minimal example below:

  #!/usr/bin/env python3
  import wx

  class StepsPane(wx.CollapsiblePane):
      def __init__(self, parent):
          wx.CollapsiblePane.__init__(self, parent, label = 'Test Pane')

          pane = self.GetPane()

          self.StepsSizer = wx.BoxSizer(wx.VERTICAL)
          AddBindStepButton = wx.Button(pane, -1, "Add Step...")
          AddBindStepButton.Bind(wx.EVT_BUTTON, self.onAddStepButton)
          self.StepsSizer.Add(AddBindStepButton, 0, wx.TOP, 10)
          self.onAddStepButton()  # Add a step 1.

          pane.SetSizer(self.StepsSizer)

      def onAddStepButton(self, _ = None):
          newSizer = self.MakeStepUI()
          self.StepsSizer.Insert(self.StepsSizer.GetItemCount()-1, newSizer, 0, wx.EXPAND|wx.ALL, 10)
          self.StepsSizer.Layout()

      def MakeStepUI(self):
          parent = self.GetPane()
          stepNumber = self.StepsSizer.GetItemCount()
          sizer = wx.BoxSizer(wx.HORIZONTAL)
          sizer.Add(wx.StaticText(parent, -1, f"Step {stepNumber}:"), 0, wx.ALIGN_CENTER_VERTICAL)
          sizer.Add(wx.TextCtrl(parent), 1)
          sizer.Add(wx.Button(parent), 0)
          return sizer

  class Main(wx.Frame):
      def __init__(self, parent):
          wx.Frame.__init__(self, parent)
          self.Sizer = wx.BoxSizer(wx.VERTICAL)
          # this is here to simulate other content in the full app that will make the window wider
          self.Sizer.Add(wx.StaticText(self, -1, label = "(other content)", size = (500,20)), 0, wx.EXPAND|wx.ALL, 10)

          sp = StepsPane(self)
          self.Sizer.Add(sp, 1, wx.EXPAND|wx.ALL, 5)
          sp.Expand()

          self.SetSizer(self.Sizer)

  class MyApp(wx.App):
      def OnInit(self):
          self.Main = Main(None)
          self.Main.Show()
          return True

  if __name__ == "__main__":
      app = MyApp(redirect=False)
      app.MainLoop()

I've tried various combinations of .Layout() on the StepsPane and StepsSizer with no difference. Calling Fit() on the StepsPane (at the end of onAddStepButton) correctly expands it vertically to contain all of the steps, but shrinks it horizontally so the TextCtrls get squashed.

Manually collapsing and expanding the StepsPane is the only thing that makes it do the desired thing. (And yes, I tried calling .Collapse() and .Expand() to simulate that process, but that makes the situation worse.)

I thought I'd gotten pretty good at sizers and calling Layout() on the right things at the right times, but somehow this one is stumping me.

Any thoughts? I'm sure it's something simple. Thanks in advance.


Solution

  • It was indeed something simple, recorded here for posterity: None of the sizers needed explicit .Layout(), I just needed to call .Layout() on the entire enclosing frame's sizer, so in __init__:

    self.Main = parent
    

    and in onAddStepButton:

    self.Main.Sizer.Layout()
    

    did the trick, though in the test code it doesn't expand the actual Frame to fit the now correctly-laid-out CollapsiblePane. In my full app, the whole thing is enclosed in a ScrolledPane which correctly expands and adds scrollbars.