How do I lay out widgets

One way to lay out your widgets in your wxPython application is to explicitly specify the position and size of every widget when it is created. Although this method is reasonably simple, over time it has a few flaws. For one thing, because widget sizes and default font sizes differ, it can be very difficult to get the positioning exactly right on all systems. In addition, you must explicitly change the position of each widget every time the user resizes the parent window. This can be a real pain to implement properly.

Fortunately, there's a better way. The layout mechanism in wxPython is called a sizer, and the idea is similar to layout managers in Java AWT and other interface toolkits. Each different sizer manages the size and position of its windows based on a set of rules. The sizer belongs to a container window (typically a wx.Panel).

Subwindows created inside the parent must be added to the sizer, and the sizer manages the size and position of each widget.

Creating a sizer

To create a sizer:

1 Create the panel or container that you want to be automatically sized.

2 Create the sizer.

3 Create your subwindows as you would normally.

4 Add each subwindow to the sizer using the sizer's Add() method. This is in addition to adding the subwindow to the parent container. When you add the window, give the sizer additional information, including the amount of space to surround the window, how to align the window within the allotted space managed by the sizer, and how to extend the window when the container window resizes.

5 Sizers can nest, meaning that you can add other sizers to the parent sizer as well as window objects. You can also set aside a certain amount of empty space as a separator.

6 Call the method SetSizer(sizer) of the container.

Table 6.8 lists the most commonly used sizers available in wxPython. For a more complete description of each particular sizer, see chapter 11.

Table 6.8 The most commonly used wxPython sizers

Sizer

Description

wx.BoxSizer

Lays children out in a line. A wx.BoxSizer can be either horizontally or vertically oriented, and can contain subsizers in any orientation to create complex layouts. Parameters passed to the sizer when items are added govern how children react when resized along either the main or perpendicular axis of the box.

wx.FlexGridSizer

A fixed two-dimensional grid, which differs from wx.GridSizer in that the size of each row and column is set separately based on the largest element in that row or column.

wx.GridSizer

A fixed two-dimensional grid, where each element is the same size—the size needed by the largest element in the sizer. When creating a grid sizer, you fix either the number of columns or the number of rows. Items are added left to right until a row is filled, and then the next row is started.

Table 6.8 The most commonly used wxPython sizers (continued)

Sizer

Description

wx.GridBagSizer

A two-dimensional grid, based on wx.FlexGridsizer. Allows for items to be placed in a specfic spot on the grid, and also allows items to span multiple grid locations.

wx.StaticBoxSizer

Identical to a wx.BoxSizer, with the one addition of a border (and optional caption) around the box.

Using a sizer

To demonstrate the use of a sizer, we'll add a control panel to the Sketch application. The control panel contains buttons for setting the color and thickness of the line. This example uses instances of both wx.GridSizer (for the buttons) and wx.BoxSizer (for the rest of the layout). Figure 6.6 displays the Sketch application with the panel, illustrating how the grid and box layouts appear in practice.

Figure 6.6 The Sketch application with an automatically laid out control panel

Listing 6.8 displays the changes to the Sketch application required to implement the control panel. The discussion in this section will focus on the sizer implementation.

Listing 6.8 Sketch sizer changes in the Sketch Frame def _init_(self, parent):

self.title = "Sketch Frame"

wx.Frame._init_(self, parent, -1, self.title, size=(800,600)) self.filename = ""

self.sketch = SketchWindow(self, -1)

self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion)

self.initStatusBar()

self.createMenuBar()

self.createToolBar()

self.createPanel()

def createPanel(self):

controlPanel = ControlPanel(self, -1, self.sketch) box = wx.BoxSizer(wx.HORIZONTAL) box.Add(controlPanel, 0, wx.EXPAND) box.Add(self.sketch, 1, wx.EXPAND) self.SetSizer(box)

In listing 6.8, the createPanel() method creates the instance of ControlPanel (described in the next listing), and puts together the box sizer. The only parameter to the constructor for wx.BoxSizer is the orientation, which can be either wx. HORIZONTAL or wx.VERTICAL. Next, the new control panel and the previously created SketchWindow are each added to the sizer using the Add() method. The first argument is the object that should be added to the sizer. The second argument is used by wx.BoxSizer as a stretch factor to determine how the sizer should resize its children when its own size changes. In the case of a horizontal sizer, the stretch factor determines how the horizontal size of each child changes (the vertical stretching is performed by the box sizer based on the flags in the third argument).

If the stretch factor is zero, the object shouldn't change size no matter what happens to the sizer. If the factor is greater than zero, that is interpreted as a share of the total size relative to the shares of the other children in the sizer (similar to how wx.StatusBar manages text field widths). If all children in the sizer have the same factor, they all resize at the same rate and equally share in the space that is left after positioning the fixed size elements. In this case, the 0 for the control panel indicates that the panel should not change horizontal size if the user stretches the frame, while the 1 for the sketch window means that all the size changes are absorbed there.

The third argument to Add() is another bitmask flag. Full details on expected flag values will be given later in the chapter. The wx.expand value is one of several values that govern how the item changes size across the axis perpendicular to the main axis for a box sizer; in this case, what happens when the frame changes size vertically. Using the wx.expand flag directs the sizer to resize the child to completely fill the available space. Other possible options allow the child to be resized proportionally or aligned to a particular part of the sizer. Figure 6.7 should help clarify which parameter governs which resize direction.

The result of these settings is that when you run the frame with this box sizer, any size change in a horizontal direction causes the sketch window to change size, and the control panel remains the same. A size change in the vertical direction causes both subwindows to expand or contract vertically.

The ControlPanel class referenced in listing 6.8 uses a combination of grid and box sizers. Listing 6.9 contains the code for that class.

Vertical Box

Widget

Widget

Widget

Horizontal Box

Resize by Flag

Horizontal Box

Widget

Widget

Widget

Resize by Factor

Figure 6.7 A drawing showing which argument determines resize behavior in each direction.

Listing 6.9 The control panel class, using grid and box sizers class ControlPanel(wx.Panel):

BMP_SIZE = 16 BMP_BORDER = 3 NUM_COLS = 4 SPACING = 4

colorList = ('Black', 'Yellow', 'Red', 'Green', 'Blue', 'Purple', 'Brown', 'Aquamarine', 'Forest Green', 'Light Blue', 'Goldenrod', 'Cyan', 'Orange', 'Navy', 'Dark Grey', 'Light Grey') maxThickness = 16

wx.Panel._init_(self, parent, ID, style=wx.RAISED_BORDER) self.sketch = sketch buttonSize = (self.BMP_SIZE + 2 * self.BMP_BORDER, self.BMP_SIZE + 2 * self.BMP_BORDER) colorGrid = self.createColorGrid(parent, buttonSize) thicknessGrid = self.createThicknessGrid(buttonSize) self.layout(colorGrid, thicknessGrid)

def createColorGrid(self, parent, buttonSize): self.colorMap = {} self.colorButtons = {}

colorGrid = wx.GridSizer(cols=self.NUM_COLS vgap=2)

for eachColor in self.colorList:

bmp = parent.MakeBitmap(eachColor) b = buttons.GenBitmapToggleButton(self, -1

size=buttonSize) b.SetBezelWidth(l) b.SetUseFocusIndicator(False) self.Bind(wx.EVT_BUTTON, self.OnSetColour, colorGrid.Add(b, 0)

self.colorMap[b.GetId()] = eachColor self.colorButtons[eachColor] = b self.colorButtons[self,colorList[0]].SetToggle(True) return colorGrid

Creating the V color grid hgap=2, bmp, b)

def createThicknessGrid(self, buttonSize): <—i - ..

.. i Creating the self_thicknessIdMap = {} C thickness grid self.thicknessButtons = {}

thicknessGrid = wx.GridSizer(cols=self.NUM_COLS, hgap=2, vgap=2)

for x in range(1, self.maxThickness + 1):

b = buttons.GenToggleButton(self, -1, str(x), size=buttonSize)

b.SetBezelWidth(1) b.SetUseFocusIndicator(False)

self.Bind(wx.EVT_BUTTON, self.OnSetThickness, b) thicknessGrid.Add(b, 0) self.thicknessIdMap[b.GetId()] = x self.thicknessButtons[x] = b self.thicknessButtons[1].SetToggle(True) return thicknessGrid d Combining def layout(self, colorGrid, thicknessGrid): <-J the grids box = wx.BoxSizer(wx.VERTICAL) box.Add(colorGrid, 0, wx.ALL, self.SPACING) box.Add(thicknessGrid, 0, wx.ALL, self.SPACING) self.SetSizer(box) box.Fit(self)

def OnSetColour(self, event):

color = self.colorMap[event.GetId()] if color != self.sketch.color:

self.colorButtons[self.sketch.color].SetToggle(False) self.sketch.SetColor(color)

def OnSetThickness(self, event):

thickness = self.thicknessIdMap[event.GetId()] if thickness != self.sketch.thickness:

self.thicknessButtons[self.sketch.thickness].SetToggle(False) self.sketch.SetThickness(thickness)

O The createColorGrid() method builds the grid sizer that contains the color buttons. First, we create the sizer itself, specifying the number of columns as four. Since the column count is set, the buttons will be laid out from left to right, and then down. Then, we take the list of colors, and create a button for each color. Inside the for loop, we create a square bitmap of the proper color, and create a toggle button with that bitmap using one set of generic button widget classes defined in the wxPython library. Then we hook the button up to an event, and add it to the grid. After that, we add it to a few dictionaries to make it easier to relate color, ID, and button in later code. We don't have to specify the button's placement within the grid; the sizer takes care of that for us.

Q The createThicknessGrid() method is almost identical to the color grid method. In fact, an enterprising programmer might be able to merge them into a common utility function. The grid sizer is created, and the sixteen buttons are added one at a time, with the sizer making sure they line up nicely on the screen.

d We use a vertical box sizer to place the grids one on top of the other. The second argument for each grid is 0, indicating that the grid sizers should not change size when the control panel stretches vertically. (Since we already know that the control panel doesn't change size horizontally, we don't need to specify the horizontal behavior.) This example shows the fourth argument to Add(), which is the width of the border to place around the item, in this case specified by the self.SPACING variable. The wx.ALL as the third argument is one of a set of flags that governs which sides to apply the border. Not surprisingly, wx.ALL says that the border should be applied on all four sides of the object. At the end, we call the Fit() method of the box sizer, with the control panel as an argument. The method tells the control panel to resize itself to match the minimum size that the sizer thinks it needs. Typically, you'll call this method as part of the creation of a window that uses sizers, to ensure that the enclosing window is large enough to encompass the sizer.

The wx.Sizer base class contains several methods common to all sizers. Table 6.9 lists the most commonly used methods.

Table 6.9 Methods of wx.Sizer

Function

Description

Add(window, proportion = 0, flag=0, border=0, userData = None)

Add(sizer, proportion=0, flag=0, border=0, userData = None)

Add(size, proportion=0, flag=0, border=0, userData = None)

Adds an item to the sizer. The first version adds a wxWindow, the second a nested sizer. The third version adds empty space which is used as a separator and is subject to the same rules for positioning as a window would be. The proportion argument manages the size amount that the window changes relative to other windows—it's only meaningful for a wx.BoxSizer. The flag argument is a bitmap with many different flags for alignment, border position, and growth. A full list is in chapter 11. The border argument is the amount of space in pixels to place around the window or sizer. userData allows you to associate data with the object, for example in a subclass that might need more information for sizing.

Fit(window) FitInside(window)

Causes the window argument to resize to the sizer's minimum size. The argument is usually the window using the sizer. The FitInside() method is similar, but instead of changing the screen display of the window, only changes its internal representation. This is used for a window inside a scroll panel to trigger scroll bar display.

GetSize()

Returns the size of the sizer as a wx.Size object.

GetPosition()

Returns the position of the sizer as a wx.Point object.

GetMinSize()

Returns the minimum size needed to fully lay out the sizer as a wx.Size object.

Layout()

Programatically forces the sizer to recalculate the size and position of its children. Call after dynamically adding or removing a child.

Prepend(... )

Identical to Add() (all three versions, but the new object is placed at the beginning of the sizer list for layout purposes).

Table 6.9 Methods of wx.sizer (continued)

Function

Description

Remove(window)

Remove(sizer)

Remove(nth)

Removes an object from the sizer. Depending on the version, either a specific object or the nth in the sizer list is removed. If this is done after startup, call Layout() after.

SetDimension(x, y, width, height)

Programatically forces the sizer to take the given size, and causes all children to reposition themselves

For more detailed information about sizers and nesting sizers, refer to chapter 11.

Was this article helpful?

0 -1

Responses

  • JAMES
    How to remove row from flex grid wxpython?
    8 years ago

Post a comment