Abstract
wxPython represents the python bindings to wxwidgets GUI toolkit. It enables you to write cross-platform Graphical User Interfaces or GUIs in python. Unlike other toolkits wxPython allows you to create GUIs that will look native on all platforms it supports (Windows, Linux, OSX).
For more information on GUI toolkits check The GUI Toolkit, Framework Page and python.org's GuiProgramming
For more information about wxPython history check WxPythonHistory
Prerequisites
Here is what you need before we start:
A working wxpython installation. More about getting wxpython installed in WxPythonInstallation.
Python knowledge. You need to know to program in python, if you don't go to LearningPython.
Basic GUI programming knowledge. You need some basic knowledge about widgets (GUI elements) and about event driven programming. More about this on GuiBasics.
An editor. You can get by with a simple editor and the command line however you could do better. Take a look at the ToolsPage and choose whatever fits your needs.
Hello wxpython
1 import wx
2 app = wx.App() # creation of the wx.App object (initialisation of the wxpython toolkit)
3 frame = wx.Frame(None, title="Hello World") # creation of a Frame with "Hello World" as title
4 frame.Show() # frames are invisible by default so we use Show() to make them visible
5 app.MainLoop() # here the app enters a loop waiting for user input
Although there are only 5 lines this app does everything a GUI is supposed to to: presents information (the "Hello World" title) and reacts to user input (you can drag the frame around the screen, you can resize it and you can minimize, maximize or close it).
Minimal App
Most of wxpython apps will go through the following scenario:
- an app object gets created, its creation will take care of the initialization of the toolkit.
- a custom frame/dialog/window gets created and shown.
the app's MainLoop() gets called and this will put the app in a loop where events generated by widgets will be dispatched to their handlers.
In most of the apps, steps 1 and 3 will be just 2 lines: step 1 will be something like app = wx.App(0) and step 3 will be something like app.MainLoop().
Step 2 will involve:
- 2.1 creating some widgets and placing them in the frame 2.2 binding events generated by these widgets to custom handlers.
Step 2.2 is rather straightforward: identify the event you want to react to using the documentation and then use widget.Bind(...) to create a link between the event generated by that widget and your custom handler.
Step 2.1 will be most of the GUI related work you'll do on your app. Most of the problems you'll encounter will be here. It will be either that your widgets don't look/behave as you want them to or maybe they aren't laid out properly, this is why it is a good idea to have the docs and the demo open at all time and to learn at least one way of laying out widgets.
The recommended way of laying out widgets is by using sizers. The sizer is a class that will compute the location and size a widget should have. Of course it requires some hints like the order in which the widgets are laid out, if a widget should enlarge as more space is available or if it should stay the same size. Sizers will automatically handle situations where the layout of the frame/dialog/window needs to change. For example, when you resize the frame and more space becomes available, the sizer will automatically recompute widgets size and location and distribute the available space to the managed widgets according to the hints that were given.
wx.BoxSizer is one of the most used sizers. It is very easy to use and with several BoxSizers you can create quite complex layouts. It simply lays out the widgets one after the other from top to bottom if the orientation is wx.VERTICAL or from left to right if the orientation is wx.HORIZONTAL. Adding widgets to the sizer means that the sizer will manage their size and position. When adding the widgets you can also give the sizer certain hints about how the widget should react to changes in layout. The first hint is the proportion, it could be 0 or 1 and above and it tells the sizer if it should expand the widget in the direction of the layout as more space is available. So if you use a BoxSizer with wx.VERTICAL as orientation a proportion of 1 will cause the widget to use all the available vertical space. If more than one widget has 1 as proportion they will share (divide equally) the available space. Using different numbers for proportions will cause the space to be divided proportionally between the widgets. The next attribute is the flag. Here you could tell the sizer if the widget should expand in the other direction, if the widgets should be aligned in the available space or if there should be a border around the widget. More about it in the wxDocs, look for wxSizer, the parent of BoxSizer. Also look at the sizer examples from the Demo.
Hello Sizers App:
1 import wx
2 import random
3
4 def GetRandColor():
5 red = random.randint(0,255)
6 green = random.randint(0,255)
7 blue = random.randint(0,255)
8 return (red, green, blue)
9
10 class ColorPanel(wx.Panel):
11 def __init__(self, parent, color='rand'):
12 wx.Panel.__init__(self, parent)
13 if color == 'rand':
14 color = GetRandColor()
15 self.Bind(wx.EVT_MOTION, self.OnMouseMove)
16 self.SetBackgroundColour(color)
17
18 def OnMouseMove(self, evt):
19 self.SetBackgroundColour(GetRandColor())
20 self.Refresh()
21 evt.Skip()
22
23 class MyFrame(wx.Frame):
24 def __init__(self):
25 wx.Frame.__init__(self, None, title="Hello Sizers")
26 vSizer =wx.BoxSizer(wx.VERTICAL)
27 hSizer = wx.BoxSizer(wx.HORIZONTAL)
28 red = ColorPanel(self, 'red')
29 blue = ColorPanel(self, 'blue')
30 green = ColorPanel(self, 'green')
31 rand = ColorPanel(self)
32
33 red.SetSize((100, 100))
34 rand.SetSize((100, 100))
35 green.SetSize((-1, 100))
36 blue.SetSize((-1, 100))
37
38 vSizer.Add(rand, 0, wx.EXPAND)
39 vSizer.Add(green, 1, wx.EXPAND)
40 vSizer.Add(blue, 2, wx.EXPAND)
41
42 hSizer.Add(red, 0, wx.EXPAND)
43 hSizer.Add(vSizer, 1, wx.EXPAND)
44
45 self.SetSizer(hSizer)
46 self.Fit()
47
48 self.Show()
49 self.Bind(wx.EVT_CLOSE, self.OnClose)
50
51 def OnClose(self, evt):
52 dlg = wx.MessageDialog(self, 'Are you sure you want to close My World?',
53 'Closing...', wx.YES_NO | wx.ICON_QUESTION)
54 ret = dlg.ShowModal()
55 dlg.Destroy()
56
57 if ret == wx.ID_YES:
58 evt.Skip()
59
60 app = wx.App(0)
61 MyFrame()
62 app.MainLoop()
Now, for some clarifications on the above app:
ColorPanel is a custom wx.Panel that can receive a color at initialization, color that will become its background color. If it doesn't receive a color it defaults to 'rand' and beside setting the background color to some random color it also installs a handler for mouse movement so that when the mouse moves OVER the panel the panel changes its background color. As you can see SetBackgroundColour is not fussy, it accepts a named color, a wx.Color object, a predefined wx.Color object like wx.RED or a tuple of 3 integers representing the intensity of red, green and blue.
The layout is taken care of in the Frame constructor. There we create 4 ColorPanels and with the help of 2 BoxSizers we place them in a configuration with the red panel on the left, expanding as the frame grows vertically. The next 3 panels are placed on the right one on top of the other with green and blue expanding vertically in a proportion of 1:2.
The sizer talks to the children (the widgets it manages) and to the parent. From the parent it gets information about available space and from the children it gets information about what sizes they prefer. When you have problems with the layout the first step in debugging is to look for what information the sizer receives. The information about the size is ALWAYS overwritten by the hints you give to the sizer. In the above example we set the size of the red panel to 100x100 pixels but when we add it to the sizer we say that proportion should be 0 and that it should EXPAND. So the sizer (being a HORIZONTAL BoxSizer) honors the information about the width that it receives from the panel due to proportion 0 (as the widget wants) BUT it overrides the information about height due to wx.EXPAND hint.
The green panel has both the proportion set to 1 and the wx.EXPAND flag so it looks like none of its size hints are gonna be honored by the sizer... well not true, having proportion of 1 makes it a reference so when we later ask the frame to Fit the following happens:
- the frame looks for its sizer, we set it to be the hSizer so it finds it.
- the frame asks the sizers for a "best" size
- the hSizer asks its children about size
- red says (100, 100) ; we have for now (100x100)
- next child is vSizer so hSizer asks it about what size it would like to have
- vSizer asks its children about size
- rand says (100, 100) so we have so far (200x100) (same height as red)
- green says it doesn't care about its width ( we used -1 to communicate that) but it would like to be 100 pixel height; it gets 100 pixels for height and 100 for width because it wanted to wx.EXPAND and the current available width was already set by rand so we have (200x200); red gets 100 more pixels in height because it too has wx.EXPAND
- blue says it doesn't care about width (gets 100 just like green) but it would like to have a height of 100... well tough luck! the proportion of 2 dictates that it should be twice the height of green so it gets 200 pixels as height; red of course complies again with the new increase in height and becomes (100x200) so... the final size is (200x400); this information gets back to the frame and the frame shows itself on the screen with that size.
Let's see now what happens when we resize the frame:
the frame gets its sizer and tells it FitInside me (FitInside is an actual method of the sizer)
- hSizer gets the size of the frame and then starts asking its children about what they need
- red says it wants 100x100, red gets 100 pixels from width and all the available height (wx.EXPAND)
- vSizer receives what width remains and all the height
- rand wants 100 height and it gets it (if available) along with all the width vSizer received
- green and blue both get ignored about their needs and get all the available width with the height divided proportionally according to the proportion arguments (blue gets twice the height green gets)
Next Level
Here are few things you might wanna try next:
Learn more about how you can learn more AdvancedDocumentation
Learn more about the layout on AdvancedLayout
Learn more about event handling on AdvancedEventHandling
Learn how you can improve your code and your workflow by harvesting the work done by others on AdvancedDesign
Take a look at the wxPython Cookbook