wxPython Sizers Tutorial: Using a GridSizer

In my last post, I created a generic form in wxPython using only wx.BoxSizers for automatic sizing of my widgets. This time, I am adding on to my previous example using a wx.GridSizer to show the following:

  • How to right-align the icon and the label
  • How to vertically align the label with the text control
  • How to keep the text controls lined up no matter what the length of the labels are.

First off, I have to thank Malcolm from the wxPython user’s group for challenging me with ideas for improvement on my last example and I also need to thank Karsten for telling me one good way to accomplish Malcolm’s right-alignment predicament. Just so you know, this is not the only way to do this and there may be a better or easier way . Any stupid code is my fault entirely.

Now, on with the show! The first item of note is the wx.GridSizer I used. Let’s take a look under the hood:

gridSizer = wx.GridSizer(rows=4, cols=2, hgap=5, vgap=5)

This tells us that I am creating a widget that will have 4 rows and 2 columns with a vertical and horizontal gap of white space of 5 pixels. You add widgets to this in a right-to-left manner such that when you’ve added a number of widgets equal to the column number, it automatically “wraps” to the next row. In other words, if I add two widgets, that constitutes one full row and the next widget I add will automatically go to the second row (and so on and so forth).

Before we add anything to the wx.GridSizer though, we need to look at how I’ve changed my wx.BoxSizers. I’ve added a spacer to the beginning of each wx.BoxSizer with a proportion of 1. This means that the spacer will take up all the left over space in the sizer. I also add the icon and label to each wx.BoxSizer and then add the wx.BoxSizer as the first item in the wx.GridSizer. But back to the spacer:

inputOneSizer.Add((20,20), proportion=1)

The spacer is just a tuple in this case. You can actually use just about any size. The first element of the tuple is the width and the second is the height. If I didn’t have the proportion set to 1, then I could mess with the size of width to position my icon and label. If you want to use a default height, use a -1. That tells wxPython to use the default.

You’ll notice that I’ve added the wx.ALIGN_CENTER_VERTICAL flag when I add my labels to the wx.BoxSizer. This is to force the label to center itself vertically and thus look like it’s also centered relative to the text controls. In some of my real programs, I’ve just made the font size larger for an effect that is quite similar.

Now we add the wx.BoxSizer to my wx.GridSizer:

gridSizer.Add(inputOneSizer, 0, wx.ALIGN_RIGHT)

Here I set the wx.ALIGN_RIGHT to make all the items “push” up against the invisible wall of the cell next to the text control. When I add the text control, I make sure I pass the wx.EXPAND flag so that it will expand when the window is resized.

Finally, I add the wx.GridSizer to my topSizer, which is a vertical wx.BoxSizer:

topSizer.Add(gridSizer, 0, wx.ALL|wx.EXPAND, 5) 

In this you should take note that I tell it to expand too. If I do not do this, than the topSizer will keep the sizers it contains from expanding. Try taking out that flag to see for yourself.

Here’s the code in full:

import wx
 
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, title='My Form')
 
        # Add a panel so it looks correct on all platforms
        self.panel = wx.Panel(self, wx.ID_ANY)
 
        bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
        titleIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
        title = wx.StaticText(self.panel, wx.ID_ANY, 'My Title')

        lblSize = (50, -1)
        
        bmp = wx.ArtProvider.GetBitmap(wx.ART_TIP, wx.ART_OTHER, (16, 16))
        inputOneIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
        labelOne = wx.StaticText(self.panel, wx.ID_ANY, 'Name')
        inputTxtOne = wx.TextCtrl(self.panel, wx.ID_ANY,'')
 
        inputTwoIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
        labelTwo = wx.StaticText(self.panel, wx.ID_ANY, 'Address')
        inputTxtTwo = wx.TextCtrl(self.panel, wx.ID_ANY,'')
 
        inputThreeIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
        labelThree = wx.StaticText(self.panel, wx.ID_ANY, 'Email')
        inputTxtThree = wx.TextCtrl(self.panel, wx.ID_ANY, '')
 
        inputFourIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
        labelFour = wx.StaticText(self.panel, wx.ID_ANY, 'Phone')
        inputTxtFour = wx.TextCtrl(self.panel, wx.ID_ANY, '')
 
        okBtn = wx.Button(self.panel, wx.ID_ANY, 'OK')
        cancelBtn = wx.Button(self.panel, wx.ID_ANY, 'Cancel')
        self.Bind(wx.EVT_BUTTON, self.onOK, okBtn)
        self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn)
 
        topSizer        = wx.BoxSizer(wx.VERTICAL)
        titleSizer      = wx.BoxSizer(wx.HORIZONTAL)
        gridSizer       = wx.GridSizer(rows=4, cols=2, hgap=5, vgap=5)
        inputOneSizer   = wx.BoxSizer(wx.HORIZONTAL)
        inputTwoSizer   = wx.BoxSizer(wx.HORIZONTAL)
        inputThreeSizer = wx.BoxSizer(wx.HORIZONTAL)
        inputFourSizer  = wx.BoxSizer(wx.HORIZONTAL)
        btnSizer        = wx.BoxSizer(wx.HORIZONTAL)
 
        titleSizer.Add(titleIco, 0, wx.ALL, 5)
        titleSizer.Add(title, 0, wx.ALL, 5)

        # each input sizer will contain 3 items
        # A spacer (proportion=1),
        # A bitmap (proportion=0),
        # and a label (proportion=0)
        inputOneSizer.Add((20,-1), proportion=1)  # this is a spacer
        inputOneSizer.Add(inputOneIco, 0, wx.ALL, 5)
        inputOneSizer.Add(labelOne, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5) 

        inputTwoSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
        inputTwoSizer.Add(inputTwoIco, 0, wx.ALL, 5)
        inputTwoSizer.Add(labelTwo, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)

        inputThreeSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
        inputThreeSizer.Add(inputThreeIco, 0, wx.ALL, 5)
        inputThreeSizer.Add(labelThree, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
 
        inputFourSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
        inputFourSizer.Add(inputFourIco, 0, wx.ALL, 5)
        inputFourSizer.Add(labelFour, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)

        # Add the 3-item sizer to the gridsizer and
        # Right align the labels and icons
        gridSizer.Add(inputOneSizer, 0, wx.ALIGN_RIGHT)
        # Set the TextCtrl to expand on resize
        gridSizer.Add(inputTxtOne, 0, wx.EXPAND)
        gridSizer.Add(inputTwoSizer, 0, wx.ALIGN_RIGHT)
        gridSizer.Add(inputTxtTwo, 0, wx.EXPAND)
        gridSizer.Add(inputThreeSizer, 0, wx.ALIGN_RIGHT)
        gridSizer.Add(inputTxtThree, 0, wx.EXPAND)
        gridSizer.Add(inputFourSizer, 0, wx.ALIGN_RIGHT)
        gridSizer.Add(inputTxtFour, 0, wx.EXPAND)
 
        btnSizer.Add(okBtn, 0, wx.ALL, 5)
        btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
 
        topSizer.Add(titleSizer, 0, wx.CENTER)
        topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
        topSizer.Add(gridSizer, 0, wx.ALL|wx.EXPAND, 5)        
        topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
        topSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)

        # SetSizeHints(minW, minH, maxW, maxH)
        self.SetSizeHints(250,300,500,400)
         
        self.panel.SetSizer(topSizer)
        topSizer.Fit(self)        
 
 
    def onOK(self, event):
        # Do something
        print 'onOK handler'
 
    def onCancel(self, event):
        self.closeProgram()
 
    def closeProgram(self):
        self.Close()
 
 
# Run the program
if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

That’s it for today. Enjoy!

Downloads
wxPython Sizer Tutorial 2