Why Does .Hide()ing and .Show()ing Panels in wxPython Result in the Sizer Changing the Layout?
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.
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开发者_StackOverflow中文版 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()
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()
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.
精彩评论