All associated wiki pages:
BoxSizerFromTheGroundUp |
||
This page's Table of Contents:
Contents
Terminology
The term "control" means exactly the same as "widget", as in "wxWidgets". Controls/widgets are displayable 2-dimensional wxPython graphic objects. There are a very few wxPython objects that are NOT intended ever to be visible: wx.DC bitmap manipulation tools and wx.Sizer tools. The generic term "window" is intentionally never used here to prevent confusion with a wx.Window control.
The wx.BoxSizer
Why Another Freakin' Sizer Tutorial ?!
There are countless BoxSizer tutorials and demo apps, but none both fully document and fully detail how all the various argument flags interact with other. The use of this sizer is exacerbated by the API design which requires logically ORing together many possible flag= constants that have unrelated effects. However, the BoxSizer always seems to perform correctly and is without any known bugs.
What Are Sizers and What Is the BoxSizer ?
Sizers are wx control auto-positioning algorithms. They are tools, not controls/widgets.They is technically termed "geometry management" tools that automatically adjust the size and position of controls/widgets according to rules. The BoxSizer is a the most basic sizer and probably the most commonly used. It is not too difficult to use once the meanings of all its flag constants are understood. Using this sizer can be intricate, whereas it is easier to grasp the principles of the fancier sizers. Understanding this BoxSizer will go a long way toward learning how to use all the other ones.
As commonplace as the BoxSizer is, it could be named better, such as "StackSizer". The BoxSizer places controls sequentially along the chosen vertical or horizontal axis, from top-to-bottom or left to-right, respectively. When using a BoxSizer, it's major axis must first be declared to be either wx.VERTICAL or wx.HORIZONTAL. This is the only axis on which control placements can be stacked end-to-end. Instantiating a BoxSizer uses this general form :
bxSzrName = wx.BoxSizer( wx.VERTICAL ) # There are no other arguments. Sizers can't have parents because they are NOT controls.
The square brackets,[ ], in the pseudocode snippet below indicate optional arguments. The curly brackets, { }, indicate the arguments' associated argument values. Even though the last three arguments are optional, it's likely that at least one or more is needed to produce an acceptable appearance. Controls of any type can be positioned by the sizer using its .Add() method so that their positions will be automatically controlled. The general syntax to .Add() a control is:
bxSzrName.Add( { control, sizer spacer or another sizer }, [ proportion=] { positive integer }, [ flag=] { a logical "OR" phrase of border padding and minor axis positioning flags }, [ border=] { a positive integer specifying border width in pixels } )
The First Step: Designing the Parent Container Widget Before Any Coding Begins
A Container Widget is any kind of control, such as a wx.Frame, a wx.Window, a wx.Panel, a wx.Dialog, a notebook leaf, etc., etc., that can hold (contain and display) another widget. It can have any kind of sizer to position-manage its child controls. In general, if a sizer is used within a container control, then all the child controls in the container should be managed by that sizer. All child controls not positioned by either a sizer or absolute positioning may end up placed in a pile of all the unplaced controls in the upper-left corner of its parent client area.
The first exercise is to lay out a Frame that consists of a StaticText and a TextCtrl. The most important positioning requirements for this app are, in my opinion, :
The StaticText is used as the caption and is centered on top of the TextCtrl.
- Both controls are centered horizontally within the parent's client area.
- Both controls have equal spacing from the client areas' four outside edges along both axes.
The code to: 1) Create the two controls (meaning to instantiate visible wxPython objects), 2) Create a BoxSizer, and then 3) Have that sizer manage the controls' positions (geometry) is as follows:
1 # The caption wx.StaticText is placed directly above a wx.TextCtrl.
2 # Note that no "pos=" positioning parameter is needed because the BoxSizer
3 # will determine its positioning.
4 # If a position were to be specified it would be ignored and overridden by the sizer.
5 #
6 caption_stTxt = wx.StaticText( self, -1, 'My TextCtrl Caption' )
7 self.listing_txtCtrl = wx.TextCtrl( self, -1, style=wx.TE_MULTILINE, size=(200, 150) )
8
9 allCtrls_vertSizer = wx.BoxSizer( wx.VERTICAL )
10
11 allCtrls_vertSizer.Add( caption_stTxt, proportion=0 )
12 allCtrls_vertSizer.Add( self.listing_txtCtrl, proportion=0 )
13
14 self.SetSizer( allCtrls_vertSizer ) # "self" must be a control with a client area
15 self.Layout() # Tell this control to tell its associated sizer to arrange all the visible controls.
Since a sizer is NOT a control it must be associated to a control, The type of control must be one that can hold and display other controls within its client area, such as a Frame, Panel, Window, SplitterWindow, etc. In the code snippet above this is done with the statement:
self.SetSizer( allCtrls_vertSizer )
where "self" is one of those controls such as a Frame, Panel, Window, SplitterWindow, etc.
The sizer has properly stacked both controls in the vertical direction starting at the top and left sides of the frame's client area..The next most important rearrangement needed to be done is to position the two controls in the horizontal center of the parent client area. To do this the flag= parameter will be used.
The wx.Frame (and wx.Notebook) is one of a few controls/widgets in that it usually has decorations. The decorations typically consist of the title text and buttons located at the top of the control. The control's border is made of individual controls that provide user-resizing handles. The area below the title bar and within the lower borders is called the client area. This is where child controls can be placed. In most of the basic controls the client area consists of all the control's area. Child controls are positioned within the parent control using relative coordinates starting at (0, 0). Frames(and wx.Dialogs) are positioned on the screen using absolute screen coordinates. The relative coordinate origin is the upper-left corner of the container control's client area. So, there are 2 coordinate systems being used: Screen coordinates and Client Area coordinates.
The flag= Argument
This argument gives additional instructions for how the BoxSizer should place a control relative to the other controls and the client area's edges. Predefined constants are used as values and each affects one of three groups of the control's placement or size.
The fact that the three nearly independent groups of flag= constants must be logically OR'ed together is s big stumbling block that newcomers encounter. The accepted Pythonic way to use a typical function call or class instantiation is to use separate arguments with names that describe their individual purposes. But, the argument flag can be assigned constant values of all three sizing and positioning constant groups, Thus, the argument name flag gives no information at all concerning for what particular purposes it's being used.
Minor Axis Positioning flags
If a BoxSizer is declared wx.VERTICAL, for example, then one of the flags wx.ALIGN_LEFT or wx.ALIGN_RIGHT may be used to position the control all the way in that direction against the border of the client area along the sizer's minor axis. Likewise, a wx.HORIZONTAL BoxSizer can be given the wx.ALIGN_TOP or wx.ALIGN_BOTTOM positioning flags. The wx.ALIGN_CENTER flag will simply center the control along the minor axis regardless of which direction the minor axis lies. major axis will be silently ignored. wx.ALIGN_LEFT # The sizer must have been defined wx.VERTICAL for this be effective. wx.ALIGN_RIGHT # For wx.VERTICAL sizers, only. wx.ALIGN_TOP # For wx.HORIZONTAL sizers, only. wx.ALIGN_BOTTOM # For wx.HORIZONTAL sizers, only wx.ALIGN_CENTER = wx.ALIGN_CENTRE # For centering along whichever is the minor axis. wx.CENTER_VERTICAL # For wx.HORIZONTAL sizers, only. It's easier to use wx.ALIGN_CENTER/RE or wx.CENTER/RE. wx.CENTER_HORIZONTAL # For wx.VERTICAL sizers, only. It's easier to use wx.ALIGN_CENTER/RE or wx.CENTER/RE. wx.EXPAND # Used with the proportion= parameter which will be covered later in this tutorial.. The two sizer .Add() statements can easily be changed to center the controls along the minor axis : The completed version of the demo program : SIMPLE_SINGLE_SIZER_2_A.PY All that's left to be done is to insert some space between the controls and the client's top edge. There are two ways to put space next to a control in order to separate it from any adjacent controls : A) Include a flag= constant value such as wx.Top in the .Add() statement as well as giving the associated border= argument with an integer pixel spacing value. Directly use one of three kinds of BoxSizer spacers just before or after .Add()ing the StaticText. Style B is much easier to both use and read. Style A will be covered later. Style B has three variations: 1) sizerName.AddSpacer( int ) This inserts a rectangular spacer of dimensions (int, int). 2) sizerName.Add( (w, h) ) This inserts a rectangular space of (w, h) dimensions. When one value is 0 the sizer inserts space only along the opposite axis, thus creating a one-dimensional spacer. 3) sizerName.AddStretchSpacer( prop= int ) This inserts a one-dimensional, self-adjusting stretchable spacer. This spacer has the property of expanding to the limit of the client area room that is not already claimed by fixed sized controls and spacers. If multiple AddStretchSpacer()s are present, the sizer will divide up the available room according to their prop= argument values. Note that .Add() has the parameter name proportion, not prop. AddStretchSpacer Example: Suppose a vertical BoxSizer is created and the total vertical client dimension (the Y axis "extent") is 100 pixels. There is a single control with .AddStretchSpacer()s on both the top of (before) and the bottom of (after) the lone control: The TextCtrl's height is 70 pixels. This leaves 30 pixels of unclaimed client space on the vertical axis to be allocated by the sizer to the two StretchSpacers. Since the prop= argument values are 3 and 7, respectively, the 30 pixels of space will be allocated to the spacers in the proportion 3 : 7, to the first and second stretch spacers, respectively. Top StretchSpacer size = (3 / (3 + 7)) * 30 pixels = 9 pixels Bottom StretchSpacer size = (7 / (3 + 7)) * 30 pixels = 21 pixels The proportion argument values used in this example were chosen only to help clarify how a BoxSizer calculates each StretchSpacer's size. Stretch spacers are much more often used to equally space both ends of a control or group of controls so that the spacing between the edges of the container and the ends of the control(s) are equal (set prop=1 for both StretchSpacers). This is what needs be done for this demo app to satisfy the third positioning requirement listed at the beginning of this section. A clever feature of sizers is that they automatically re-adjust all their control placements when its container is resized either by the user or programmatically. In the frame shown below I, a user, have manually resized the frame. The controls' vertical (sizer's major axis) centering is maintained by the two stretch spacers. The horizontal (sizer's minor axis) centering is maintained by the two flag= parameters : We've covered the BoxSizer alignment flag parameters and two of the three kinds of spacers. In the next section we'll explore achieving border spaces on any or all of the four edges of a single control rather than inserting spacers in between a pair of controls. allCtrls_vertSizer.Add( cap_stTxt, flag=wx.ALIGN_CENTER ) # May substitute '''wx.ALIGN_CENTRE'''
allCtrls_vertSizer.Add( self.myListing_txtCtrl, flag=wx.ALIGN_CENTER )
1 self.SetClientSize( (123, 100) )
2 ...
3
4 self.listing_txtCtrl = wx.TextCtrl( self, size=(83, 70), style=wx.TE_MULTILINE ) # The X axis dimension, 83, is not important.
5
6 allCtrls_vertSizer = wx.BoxSizer( wx.VERTICAL )
7
8 allCtrls_vertSizer.AddStretchSpacer( prop=3 )
9 allCtrls_vertSizer.Add( self.listing_txtCtrl, flag=wx.CENTRE )
10
11 allCtrls_vertSizer.AddStretchSpacer( prop=7 )
12
13 self.SetSizer( allCtrls_vertSizer ) # Indicate that the container control is to use this sizer.
14 self.Layout() # Invoke the sizer on every wx.EVT_SIZE event. This event first happens on a Frame's .Show()
1 allCtrls_vertSizer = wx.BoxSizer( wx.VERTICAL )
2
3 allCtrls_vertSizer.AddStretchSpacer( prop=1 ) # for major axis (vertical) centering
4
5 allCtrls_vertSizer.Add( caption_stTxt, flag=wx.ALIGN_CENTRE ) # for minor axis (horizontally for allCtrls_vertSizer) centering
6 allCtrls_vertSizer.Add( self.listing_txtCtrl, flag=wx.ALIGN_CENTER )
7
8 allCtrls_vertSizer.AddStretchSpacer( prop=1 ) # for major axis (vertical) centering of the 2 controls
9
10 self.SetSizer( allCtrls_vertSizer )
11 self.Layout() # Invoke the sizer on every wx.EVT_SIZE event. This event happens on .Show()