wxSizers are a powerful layout mechanism that most people think are fairly simple as well, but unfortunately getting to know them enough to call them simple is usually an uphill battle. In this article I've tried to address some of the common misconceptions and pitfalls that people have with sizers. If you run into any more please feel free to add to the list once you figure it out.
Here are a few facts and hints about sizers:
- If a wxFrame (or derived class) is to have only one child, then you don't need a sizer at all, as the default behaviour of the frame's EVT_SIZE handler (assuming there is only one child) is to resize that child to occupy all of the frame's client area. Often it makes a lot of sense to give a frame just one wxPanel as a child and then place other windows and controls on that panel as appropriate, and set the sizer on the panel as well.
- In normal use the sizers will treat a window's initial size when it is Add'ed to the sizer as its minimum size, and will use that size to calculate layout. Several window types default to (0,0) initial size so if you don't give them another size that is what the sizer will use for the minimum. If the sizer has no other reason to enlarge the window then you will see nothing of it.
- Often sizer.Fit(frame) is NOT what you want. It will use the minimum size of all the contained windows and sub-sizers and resize the frame so it just barely fits those minimums. More often I've found that I would rather use the default size of a frame (or the user-set size that I've loaded from a config file) and allow the sizer to layout to that size instead.
- The actual layout of windows controlled by a sizer (or layout constraints for that matter) happens in the default EVT_SIZE handler for a window. In other words, when EVT_SIZE happens, if you haven't connected your own size handler, and if the window has had a sizer assigned, and if auto-layout has been turned on, then the window's Layout method is called where it uses the assigned sizer
- Not all windows have this default behaviour for the EVT_SIZE handler. Off the top of my head, those that do have it are wxFrame
(and other frame types), wxDialog, wxPanel and wxScrolledWindow. wxSplitterWindow, and wxNotebook; and some others do specialized layout of their children, but if the children have sizers of their own then it should work as expected. Notice that wxWindow is not on the list (for various reasons), but if needed you can still allow it (or your own class derived directly form wxWindow) to have sizers and do auto layout by hooking EVT_SIZE and doing something like this:
Since Layout doesn't happen until there is a size event, you will sometimes have to force the issue by calling Layout yourself. For example, if a frame is given its size when it is created, and then you add child windows to it, and then a sizer, and finally Show it, then it may not receive another size event (depending on platform) in order to do the initial layout. Simply calling self.Layout from the end of the frame's __init__ method will usually resolve this.
- If a window managed by a sizer changes its content and you want to change the minimum size that the sizer uses for it, you can call
sizer.SetItemMinSize(window). You'll probably want to also call parentWindow.Layout() (or whomever owns the sizer) as well so the changes become visible. Starting in wxPython 2.3.3 you can easily find the sizer that is managing a window with the GetContainingSizer method.
- When using box sizers you specify either wxHORIZONTAL or wxVERTICAL, which is the orientation with which items are laid out. When an item is Add'ed with the wxEXPAND flag, the item will be resized to fill its alloted area in the opposite orientation. (So, for example, for a vertical box sizer items with wxEXPAND will expand in the horizontal direction.) The "proportion" parameter for box sizers indicates if the item is stretchable in the main orientation of the sizer and how much of the available space is taken up by the item. If the proportion is zero, then the item's minimum size will be used; if it is one, then it will get one share of the available size (after fixed size items are calculated). If 2 is used, then it will get two shares, etc. For example, if you have 3 items and you want the first to take 50% of the space and the remaining two to get 25% each then you would use proportions of 2, 1, 1 respectively.
- Sizers can be nested within each other to acheive more complex layouts. In fact, it's been said that most common layouts can be acheived by nesting various vertical and horizontal box sizers. The "items" that can be Add'ed to a sizer are windows, sizers or empty space specified by using a width and height.
- Empty borders can be placed around items by using one or more of the wxALL, wxLEFT, wxRIGHT, wxTOP, and wxBOTTOM flags of the Add method and giving the width in pixels in the border parameter.
- A sample is worth a thousand words:
1 #!python 2 import wx 3 4 class MyFrame(wx.Frame): 5 def __init__(self, parent, ID, title): 6 wx.Frame.__init__(self, parent, ID, title, size=(300, 250)) 7 8 panel1 = wx.Panel(self,-1, style=wx.SUNKEN_BORDER) 9 panel2 = wx.Panel(self,-1, style=wx.SUNKEN_BORDER) 10 11 panel1.SetBackgroundColour("BLUE") 12 panel2.SetBackgroundColour("RED") 13 14 box = wx.BoxSizer(wx.VERTICAL) 15 box.Add(panel1, 2, wx.EXPAND) 16 box.Add(panel2, 1, wx.EXPAND) 17 18 self.SetAutoLayout(True) 19 self.SetSizer(box) 20 self.Layout() 21 22 23 app = wx.PySimpleApp() 24 frame = MyFrame(None, -1, "Sizer Test") 25 frame.Show() 26 app.MainLoop()
Sizers with static boxes around them (wxStaticBox) are created in two parts:
LearnSizers1 is a small script that shows how to use sizers with wxPython.
wxSizer in python provides information about using sizers that effectively resizes items when the window is resized
Visual Sizer .Add Summary
Someone let me know if y'all can read this. It's a reference, a summary. It assumes you already know something about sizers. -- LionKimbro 2003-08-13 16:40:06
The light green text is too faint on my screen to read without putting my nose on the monitor. Other than that it's a great diagram!
Point #1 does not work for me at all (wxMSW,18.104.22.168). The Frame does not want to autosize its child. However, the sample in #11 is worth waiting for.
-- Terrel Shumway
Yes it does. Try the new sample above.
Ah Hah! I was creating the notebook pages as children of the Frame, rather than as children of the notebook. Doh!
I love samples. Thanks.
Any sample custom sizer using PySizer?, also any sample for .Remove() (Detach() seems not included on 2.5.1), and .Insert()?, I was trying to make a vertical sizer to 'hide' and shift some horizontal sizer based on horizontal sizers visibility, but I'm having problems Remove and Insert.
I find that the easiest order and organization to do things in is:
- Create widgets and set any properties needed (Do this top-down...containers to containees)
- Create sizers
- Assign sizers to their parent widgets
- Build interface by adding widgets to sizers (Do this bottom-up...containees to containers)
Step one is top-down to ensure that parent widgets are created before children, so that their children can be created w/ the appropriate parent reference. Step 4 is done bottom up because it makes more sense in my head to assemble the smaller parts and then assemble them into bigger parts.
For fellow newbies:
The order of arguments for sizer.Add() is confusing. The 2nd argument is proportion which (in my limited experience) is rarely used. The 3rd argument is the sizer flag which is used often. So I keep making this mistake: s.Add(someItem, wx.EXPAND), which is wrong.
The correct way is this: a.Add(someItem, 0, wx.EXPAND) or this: a.Add(someItem, flag=wx.EXPAND).
N.B. proportion is an integer, not a float. If you specify a float, it will be silently converted to an int and you may not get what you expected. I had mistakenly normalized everything to 1.0 and it didn't work. (Seems like it really ought to be a float...)
I've added a page on ResizingFramesUsingSizers.