Why Does .Hide()ing and .Show()ing Panels in wxPython Result in the Sizer Changing the Layout?

12,025

Solution 1

I think I figured it out. Instead of calls to the Show and Hide methods of the panels, you need to call the Show and Hide methods of the root sizer:

self.Show()

becomes

self.GetParent().GetSizer().Show(self)

...and so on.

Also, after each call, you need

self.GetParent().GetSizer().Layout()

Solution 2

Yeah, I know this is already answered, but here you go anyway:

You should only have to call Layout() on the panel's parent, so something like self.GetParent().Layout() should do the trick. See this article: http://www.blog.pythonlibrary.org/2010/06/16/wxpython-how-to-switch-between-panels/

If you want the buttons to always show, create two panels in one vertical sizer. The one on top will show your panels and the one on the bottom will show the buttons. Then use PubSub or something to communicate between them.

Share:
12,025
MetaHyperBolic
Author by

MetaHyperBolic

n00b

Updated on June 05, 2022

Comments

  • MetaHyperBolic
    MetaHyperBolic almost 2 years

    As referenced in my previous question, I am trying to make something slightly wizard-like in function. I have settled on a single frame with a sizer added to it. I build panels for each of the screens I would like users to see, add them to the frame's sizer, then switch between panels by .Hide()ing one panel, then calling a custom .ShowYourself() on the next panel. Obviously, I would like the buttons to remain in the same place as the user progresses through the process.

    I have linked together two panels in an infinite loop by their "Back" and "Next" buttons so you can see what is going on. The first panel looks great; tom10's code worked on that level, as it eschewed my initial, over-fancy attempt with borders flying every which way. And then the second panel seems to have shrunk down to the bare minimum. As we return to the first panel, the shrinkage has occurred here as well. Why does it look fine on the first panel, but not after I return there? Why is calling .Fit() necessary if I do not want a 10 pixel by 10 pixel wad of grey? And if it is necessary, why does .Fit() give inconsistent results?

    This infinite loop seems to characterize my experience with this: I fix the layout on a panel, only to find that switching ruins the layout for other panels. I fix that problem, by using sizer_h.Add(self.panel1, 0) instead of sizer_h.Add(self.panel1, 1, wx.EXPAND), and now my layouts are off again.

    So far, my "solution" is to add a mastersizer.SetMinSize((475, 592)) to each panel's master sizer (commented out in the code below). This is a cruddy solution because 1) I have had to find the numbers that work by trial and error (-5 pixels for the width, -28 pixels for the height). 2) I don't understand why the underlying issue still happens.

    What's the correct, non-ugly solution? Instead of adding all of the panels to the frame's sizer at once, should switching panels involve .Detach()ing that panel from the frame's sizer and then .Add()ing the next panel to the frame's sizer? Is there a .JustMakeThisFillThePanel() method hiding somewhere I have missed in both the wxWidgets and the wxPython documents online?

    I'm obviously missing something in my mental model of layout. Minimalist code pasted below.

    enter image description here

    import wx
    import sys
    
    
    class My_App(wx.App):
    
        def OnInit(self):
            self.frame = My_Frame(None)
            self.frame.Show()
            self.SetTopWindow(self.frame)
            return True
    
        def OnExit(self):
            print 'Dying ...'
    
    
    class My_Frame(wx.Frame):
    
        def __init__(self, image, parent=None,id=-1, title='Generic Title', pos=wx.DefaultPosition, style=wx.CAPTION | wx.STAY_ON_TOP):     
    
            size = (480, 620)
            wx.Frame.__init__(self, parent, id, 'Program Title', pos, size, style)
    
            sizer_h = wx.BoxSizer(wx.HORIZONTAL)
    
            self.panel0 = User_Interaction0(self)       
            sizer_h.Add(self.panel0, 1, wx.EXPAND)
    
            self.panel1 = User_Interaction1(self)       
            sizer_h.Add(self.panel1, 1, wx.EXPAND)
    
            self.SetSizer(sizer_h)
    
            self.panel0.ShowYourself()
    
        def ShutDown(self):
            self.Destroy()
    
    
    class User_Interaction0(wx.Panel):
    
        def __init__(self, parent, id=-1):
    
            wx.Panel.__init__(self, parent, id)
    
            # master sizer for the whole panel
            mastersizer = wx.BoxSizer(wx.VERTICAL)
            #mastersizer.SetMinSize((475, 592))
            mastersizer.AddSpacer(15)
    
    
            # build the top row
            txtHeader = wx.StaticText(self, -1, 'Welcome to This Boring\nProgram', (0, 0))
            font = wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.BOLD)
            txtHeader.SetFont(font)
            txtOutOf = wx.StaticText(self, -1, '1 out of 7', (0, 0))                
            rowtopsizer = wx.BoxSizer(wx.HORIZONTAL)
            rowtopsizer.Add(txtHeader, 3, wx.ALIGN_LEFT) 
            rowtopsizer.Add((0,0), 1)  
            rowtopsizer.Add(txtOutOf, 0, wx.ALIGN_RIGHT) 
            mastersizer.Add(rowtopsizer, 0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=15) 
    
    
            # build the middle row
            text = 'PANEL 0\n\n'
            text = text + 'This could be a giant blob of explanatory text.\n'
    
            txtBasic = wx.StaticText(self, -1, text)
            font = wx.Font(11, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
            txtBasic.SetFont(font)
            mastersizer.Add(txtBasic, 1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=15)  
    
    
            # build the bottom row
            btnBack = wx.Button(self, -1, 'Back')
            self.Bind(wx.EVT_BUTTON, self.OnBack, id=btnBack.GetId())
            btnNext = wx.Button(self, -1, 'Next')
            self.Bind(wx.EVT_BUTTON, self.OnNext, id=btnNext.GetId())
            btnCancelExit = wx.Button(self, -1, 'Cancel and Exit')
            self.Bind(wx.EVT_BUTTON, self.OnCancelAndExit, id=btnCancelExit.GetId())
            rowbottomsizer = wx.BoxSizer(wx.HORIZONTAL)
            rowbottomsizer.Add(btnBack, 0, wx.ALIGN_LEFT)
            rowbottomsizer.AddSpacer(5)
            rowbottomsizer.Add(btnNext, 0)
            rowbottomsizer.AddSpacer(5)
            rowbottomsizer.AddStretchSpacer(1)
            rowbottomsizer.Add(btnCancelExit, 0, wx.ALIGN_RIGHT)
            mastersizer.Add(rowbottomsizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=15)
    
            # finish master sizer
            mastersizer.AddSpacer(15)   
            self.SetSizer(mastersizer)
    
            self.Raise()
            self.SetPosition((0,0))
            self.Fit()  
            self.Hide()
    
    
        def ShowYourself(self):
            self.Raise()
            self.SetPosition((0,0))
            self.Fit()
            self.Show()
    
    
        def OnBack(self, event):
            self.Hide()
            self.GetParent().panel1.ShowYourself()
    
        def OnNext(self, event):
            self.Hide()
            self.GetParent().panel1.ShowYourself()
    
        def OnCancelAndExit(self, event):
            self.GetParent().ShutDown()
    
    
    class User_Interaction1(wx.Panel):
    
        def __init__(self, parent, id=-1):
    
            wx.Panel.__init__(self, parent, id)
    
            # master sizer for the whole panel
            mastersizer = wx.BoxSizer(wx.VERTICAL)
            #mastersizer.SetMinSize((475, 592))
            mastersizer.AddSpacer(15)
    
    
            # build the top row
            txtHeader = wx.StaticText(self, -1, 'Read about This Boring\nProgram', (0, 0))
            font = wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.BOLD)
            txtHeader.SetFont(font)
            txtOutOf = wx.StaticText(self, -1, '2 out of 7', (0, 0))                
            rowtopsizer = wx.BoxSizer(wx.HORIZONTAL)
            rowtopsizer.Add(txtHeader, 3, wx.ALIGN_LEFT) 
            rowtopsizer.Add((0,0), 1)  
            rowtopsizer.Add(txtOutOf, 0, wx.ALIGN_RIGHT) 
            mastersizer.Add(rowtopsizer, 0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=15) 
    
    
            # build the middle row
            text = 'PANEL 1\n\n'
            text = text + 'This could be a giant blob of boring text.\n'
    
            txtBasic = wx.StaticText(self, -1, text)
            font = wx.Font(11, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
            txtBasic.SetFont(font)
            mastersizer.Add(txtBasic, 1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=15)  
    
    
            # build the bottom row
            btnBack = wx.Button(self, -1, 'Back')
            self.Bind(wx.EVT_BUTTON, self.OnBack, id=btnBack.GetId())
            btnNext = wx.Button(self, -1, 'Next')
            self.Bind(wx.EVT_BUTTON, self.OnNext, id=btnNext.GetId())
            btnCancelExit = wx.Button(self, -1, 'Cancel and Exit')
            self.Bind(wx.EVT_BUTTON, self.OnCancelAndExit, id=btnCancelExit.GetId())
            rowbottomsizer = wx.BoxSizer(wx.HORIZONTAL)
            rowbottomsizer.Add(btnBack, 0, wx.ALIGN_LEFT)
            rowbottomsizer.AddSpacer(5)
            rowbottomsizer.Add(btnNext, 0)
            rowbottomsizer.AddSpacer(5)
            rowbottomsizer.AddStretchSpacer(1)
            rowbottomsizer.Add(btnCancelExit, 0, wx.ALIGN_RIGHT)
            mastersizer.Add(rowbottomsizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=15)
    
            # finish master sizer
            mastersizer.AddSpacer(15)   
            self.SetSizer(mastersizer)
    
            self.Raise()
            self.SetPosition((0,0))
            self.Fit()  
            self.Hide()
    
    
        def ShowYourself(self):
            self.Raise()
            self.SetPosition((0,0))
            self.Fit()
            self.Show()
    
    
        def OnBack(self, event):
            self.Hide()
            self.GetParent().panel0.ShowYourself()
    
        def OnNext(self, event):
            self.Hide()
            self.GetParent().panel0.ShowYourself()
    
        def OnCancelAndExit(self, event):
            self.GetParent().ShutDown()
    
    
    def main():
        app = My_App(redirect = False)
        app.MainLoop()
    
    
    if __name__ == '__main__':
        main()
    
  • MetaHyperBolic
    MetaHyperBolic about 14 years
    I did that. I moved the statement self.SetSizer(sizer_h) in the My_Frame class up before the panels are created, or else self.GetParent().GetSizer() is returns None. After that, it definitely finds the sizer. But, self.GetParent().GetSizer().Show(self) returns "False" on the first call and I see both panels crammed together on the frame. Once I start clicking the Back and Next buttons, we're back to the same behavior of pulling the panel size down to something much smaller than the frame. At that point, self.GetParent().GetSizer().Show(self) is returning True in the ShowYourself method.
  • MetaHyperBolic
    MetaHyperBolic about 14 years
    Ah, and I was tweaking my program when you added Layout! About to give that a shot.
  • MetaHyperBolic
    MetaHyperBolic about 14 years
    That worked! That must only be called at the end of the ShowYourself() function. So, my conceptual problem was that I was ignoring the hierarchy of sizers, and your solution is that these too must be called. Do you have an Amazon wish list where I could buy you a book? Seriously, this particular problem had me quite down.
  • Nathan Osman
    Nathan Osman about 14 years
    No problem, I sometimes spend hours trying to find out why some block of wxWidgets code isn't working. (Or wxPython for that matter) Just glad I could help. Oh, and if you wanted, you could upvote my answer. (By clicking the up arrow beside it :)
  • MetaHyperBolic
    MetaHyperBolic about 14 years
    That's weird. It says I'm not registered when I tried to upvote. This has happened before. Time to go dig around in the FAQ.
  • Nathan Osman
    Nathan Osman about 14 years
    Oh. You have to register an OpenID to vote and get other priviledges. There should be a link at the top of the page for registering.
  • MetaHyperBolic
    MetaHyperBolic about 14 years
    Yeah, I ran into the some time back, then dug around in the Meta section and found a "you don't have to do it, you can do whatever on the site without it" answer. Guess that's wrong. I'll go figure out this OpenID stuff.
  • Nathan Osman
    Nathan Osman about 14 years
    Thanks! Feel free to post more questions if run into anything else, and I'll be glad to help.
  • Gonzo
    Gonzo almost 6 years
    Thank you for mentioning this! It was much cleaner for me to call Layout() on the parent than what the other answer suggested