I am a Senior Reservoir Engineer at Maersk Oil in Copenhagen, Denmark, where I mix a 10-years reservoir engineering knowledge with the power of Python and its libraries to solve many everyday problems arising during the numerical studies of oil and gas fields.
I am the author and the maintainer of the AGW (Advanced Generic Widgets) library for wxPython, a large collection of owner-drawn and custom widgets shipped officially with every new release of wxPython.
Contacts:
This tutorial is a quick introduction to wxPython, divided in two parts:
Sources are available here. Figures are in the figures directory and all scripts are located in the scripts directory. The tutorial is also online at my web page.
Tutorial location
All code and material is licensed under a Creative Commons Attribution 3.0 United States License (CC-by) http://creativecommons.org/licenses/by/3.0/us
wxPython is one of the most famous frameworks used to build graphical user interfaces (GUIs) in Python. It provides native look and feel widgets on all supported platforms (Windows, Linux/Unix, Mac) and it has a vast repository of owner-drawn controls. In addition, the wxPython demo is the place where to start looking for examples and source code snippets.
If you plan to run the various scripts available in this tutorial directly from your preferred editor, you should check that it does not interfere with the wxPython event loop. Eclipse, Wingware IDE, Editra, Ulipad, Dr. Python and newest versions of IDLE (and many other editors) support this functionality. If your preferred editor does not - you can easily find out by running the Hello World sample and see if it hangs - you can still run the samples via the command line:
$ python hello_world.py
This tutorial contains many links to the documentation referring to the next generation of wxPython - codenamed Phoenix. The reasons behind this choice are:
Choose an installer that matches the version of Python you will be using. If you are using a 64-bit version of Python then make sure you also get a 64-bit wxPython, otherwise choose a 32-bit installer even if you are on a 64-bit version of Windows. There is no longer a separate ansi and Unicode build, it’s all Unicode now.
Installer | Architecture |
---|---|
wxPython2.9-win32-py26 | 32-bit Python 2.6 |
wxPython2.9-win64-py26 | 64-bit Python 2.6 |
wxPython2.9-win32-py27 | 32-bit Python 2.7 |
wxPython2.9-win64-py27 | 64-bit Python 2.7 |
This installer contains the infamous wxPython demo, other samples, and wxWidgets documentation.
The wxPython binaries for OSX are mountable disk images. Simply double click to mount the image and then run the installer application in the image. Be sure to download the image that matches the version of Python that you want to use it with. The files with “carbon” in the name use the Carbon API for implementing the GUI, are compatible with PPC and i386 machines are will work on OSX 10.4 and onwards. The file with “cocoa” in the name use the Cocoa API for implementing the GUI, requires at least OSX 10.5, and supports either 32-bit or 64-bit architectures.
These disk images contain the wxPython demo, the documentation, some other samples and some tools:
To get prebuilt binaries for Linux or other platforms, please search in your distro’s package repository, or any 3rd party repositories that may be available to you. Ubuntu users can get information about the the wx APT repository here. If all else fails you can build wxPython yourself from the source code, see the build instructions.
In this section, we are going to build step by step a skeleton of a wxPython application, enriching it incrementally. Every sub-section contains one or more exercises for you to familiarize yourself with the wxPython framework.
Documentation
As in (almost) all every other language and library, this is the simplest “Hello World” application you can write in wxPython:
# In every wxPython application, we must import the wx library
import wx
# Create an application class instance
app = wx.App()
# Create a frame (i.e., a floating top-level window)
frame = wx.Frame(None, -1, 'Hello world')
# Show the frame on screen
frame.Show()
# Enter the application main loop
app.MainLoop()
The last line enters what wxPython defines as “MainLoop”. A “MainLoop” is an endless cycle that catches up all events coming up to your application. It is an integral part of any windows GUI application.
Although the code is very simple, you can do a lot of things with your window. You can maximize it, minimize it, move it, resize it. All these things have been already done for you by the framework.
Exercises
Using the Hello World sample:
Observe what happens in (1) and (2) when you close the first frame. Does the application terminate?
Click on the figure for the solution.
Documentation
Reacting to events in wxPython is called event handling. An event is when “something” happens on your application (a button click, text input, mouse movement, a timer expires, etc...). Much of GUI programming consists of responding to events.
You link a wxPython object to an event using the Bind() method:
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title)
# Other stuff...
menu_item = file_menu.Append(wx.ID_EXIT, 'E&xit', 'Exit the application')
self.Bind(wx.EVT_MENU, self.OnExit, menu_item)
This means that, from now on, when the user selects the “Exit” menu item, the method OnExit will be executed. wx.EVT_MENU is the “select menu item” event. wxPython understands many other events (everything that starts with EVT_ in the wx namespace).
The OnExit method has the general declaration:
def OnExit(self, event):
# Close the frame, cannot be vetoed if force=True
self.Close(force=True)
Here event is an instance of a subclass of wx.Event. For example, a button-click event - wx.EVT_BUTTON - is a subclass of wx.Event.
Working with events is straightforward in wxPython. There are three steps:
Sometimes we need to stop processing an event: for example, think about a user closing your main application window while the GUI still contains unsaved data. To do this, we call the method Veto() on an event, inside an event handler:
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title)
# Other stuff...
self.Bind(wx.EVT_CLOSE, self.OnClose)
def OnClose(self, event):
# This displays a message box asking the user to confirm
# she wants to quit the application
dlg = wx.MessageDialog(self, 'Are you sure you want to quit?', 'Question',
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
if dlg.ShowModal() == wx.ID_YES:
self.Destroy()
else:
event.Veto()
Documentation
We are going to add an editable text box inside our frame (a wx.TextCtrl). By default, a text box is a single-line field, but the wx.TE_MULTILINE parameter allows you to enter multiple lines of text.
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title)
self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
A couple of things to notice:
We can create a link between the wx.TextCtrl behaviour and the menu selected by the user by binding menu events (like “Cut”, “Copy” and “Paste” menu selections) to the main frame and process the results in an event handler. For example:
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title)
self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
# Other stuff, menubar creation, etc...
# Bind the "copy menu event" to the OnCopy method
self.Bind(wx.EVT_MENU, self.OnCopy, id=wx.ID_COPY)
def OnCopy(self, event):
# See if we can copy from the text box...
if self.control.CanCopy():
# Actually copy the wx.TextCtrl content
# into the clipboard
self.control.Copy()
In the OnCopy event handler we simply check if we can copy text from the text box (i.e., if something is selected and can be copied to the clipboard) and we actually copy what is selected into the clipboard.
Hints
Exercises
Using the Widgets sample:
Click on the figure for the solution.
Layout management: describes how widgets are laid out in an application’s user interface.
wxPython provides a few alternatives:
Sizers, as represented by the wx.Sizer class and its descendants in the wxPython class hierarchy, have become the method of choice to define the layout of controls in parent windows because of their ability to create visually appealing frames independent of the platform, taking into account the differences in size and style of the individual controls.
The layout algorithm used by sizers in wxPython is closely related to layout systems in other GUI toolkits, such as Java’s AWT, the GTK toolkit or the Qt toolkit. It is based upon the idea of individual subwindows reporting their minimal required size and their ability to get stretched if the size of the parent window has changed. This will most often mean that the programmer does not set the start-up size of a window, the window will rather be assigned a sizer and this sizer will be queried about the recommended size.
This sizer in turn will query its children (which can be normal windows, empty space or other sizers) so that a hierarchy of sizers can be constructed.
Note
wx.Sizer does not derive from wx.Window and thus does not interfere with tab ordering and requires very few resources compared to a real window on screen.
What makes sizers so well fitted for use in wxPython is the fact that every control reports its own minimal size and the algorithm can handle differences in font sizes or different window sizes on different platforms without problems.
For example, if the standard font as well as the overall design of Linux/GTK widgets requires more space than on Windows, the initial window size will automatically be bigger on Linux/GTK than on Windows.
There are currently 6 different kinds of sizers available in wxPython:
Each represents a certain way to lay out window items or it fulfils a special task such as wrapping a static box around a window item (or another sizer).
Note
Note that only some controls can calculate their size (such as a wx.CheckBox) whereas others (such as a wx.ListBox) don’t have any natural width or height and thus require an explicit size. Some controls can calculate their height, but not their width (e.g. a single line text control).
All sizers are containers, i.e. they are used to lay out one window item (or several window items), which they contain. Such items are sometimes referred to as the children of the sizer. Independent of how the individual sizers lay out their children, all children have certain features in common:
A minimal size: This minimal size is usually identical to the initial size of the controls and may either be set explicitly in the wx.Size field of the control constructor or may be calculated by wxPython, typically by setting the height and/or the width of the item to -1.
A border: The border is just empty space and is used to separate widgets in a parent window. This border can either be all around, or at any combination of sides such as only above and below the control. The thickness of this border must be set explicitly, typically 5 points. The following samples show frames with only one item (a button) and a border of 0, 5, and 10 pixels around the button:
An alignment: Often, a widget is given more space than its minimal size plus its border. Depending on what flags are used for the respective widget, this widget can be made to fill out the available space entirely, i.e. it will grow to a size larger than the minimal size, or it will be moved to either the centre of the available space or to either side of the space. The following sample shows a listbox and three buttons in a horizontal box sizer; one button is centred, one is aligned at the top, one is aligned at the bottom:
A stretch factor: If a sizer contains more than one child and it is offered more space than its children and their borders need, the question arises how to distribute the surplus space among the children. For this purpose, a stretch factor (proportion) may be assigned to each child, where the default value of 0 indicates that the child will not get more space than its requested minimum size.
A value of more than zero is interpreted in relation to the sum of all stretch factors in the children of the respective sizer, i.e. if two children get a stretch factor of 1, they will get half the extra space each independent of whether one control has a minimal sizer inferior to the other or not. The following sample shows a frame with three buttons, the first one has a stretch factor of 1 and thus gets stretched, whereas the other two buttons have a stretch factor of zero and keep their initial width:
Let’s take a look at wx.BoxSizer. This is the most simple type of box sizer, and the way we add widgets to it is explained by looking at the wx.BoxSizer.Add signature:
Appends a child to the sizer.
Parameters: |
|
---|---|
Return type: |
Let’s create a vertical sizer (children will be placed on top of each other) and place two buttons in it. All the “extra” parameters are set to 0; we’ll worry about them later.
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(wx.Button(self, -1, 'An extremely long button text'), 0, 0, 0)
sizer.Add(wx.Button(self, -1, 'Small button'), 0, 0, 0)
self.SetSizer(sizer)
You’ll notice a couple of things about this:
Let’s worry about the second issue first. To make the window size more appropriate, we can set the size hints to tell the enclosing window to adjust to the size of the sizer:
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(wx.Button(self, -1, 'An extremely long button text'), 0, 0, 0)
sizer.Add(wx.Button(self, -1, 'Small button'), 0, 0, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
This is particularly useful in circumstances like this one, in which the wx.Frame‘s default size would otherwise be much too big or small to show most layouts in an aesthetically pleasing manner.
The first parameter to wx.BoxSizer.Add is obviously the wx.Window or wx.Sizer that you are adding. The second one (the proportion) defines how large the sizer’s children are in relation to each other. In a vertical sizer, this changes the height; in a horizontal sizer, this changes the width. Here are some examples:
Code | Resulting Image |
---|---|
sizer = wx.BoxSizer(wx.VERTICAL)
# Second button is three times as tall as first button
sizer.Add(wx.Button(self, -1, 'An extremely long button text'), 1, 0, 0)
sizer.Add(wx.Button(self, -1, 'Small button'), 3, 0, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
|
Same code as above, with window resized. Notice that the bottom button is still three times as tall as the top button. | |
sizer = wx.BoxSizer(wx.VERTICAL)
# First button is 3/2 the height of the second button
sizer.Add(wx.Button(self, -1, 'An extremely long button text'), 3, 0, 0)
sizer.Add(wx.Button(self, -1, 'Small button'), 2, 0, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
If one of the proportion parameters is 0, that wx.Window will be the minimum size, and the others will resize proportionally:
Code | Resulting Image |
---|---|
sizer = wx.BoxSizer(wx.VERTICAL)
# Third button is twice the size of the second button
sizer.Add(wx.Button(self, -1, 'An extremely long button text'), 0, 0, 0)
sizer.Add(wx.Button(self, -1, 'Small button'), 1, 0, 0)
sizer.Add(wx.Button(self, -1, 'Another button'), 2, 0, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
|
Same code as above, with window resized. The top button (proportion 0) is still the minimum height, and the third button is still twice the height of the second. |
This is especially useful when you want, for example, a button at the bottom which is only as big as necessary, and some other control that occupies the rest of the frame. To do so, give the button proportion 0 and the other control a number greater than 0. Mac users in particular will appreciate you for not creating huge aqua-styled buttons.
The flag argument accepted by wx.Sizer.Add is a OR-combination of the following flags. Two main behaviours are defined using these flags. One is the border around a window: the border parameter determines the border width whereas the flags given here determine which side(s) of the item that the border will be added. The other flags determine how the sizer item behaves when the space allotted to the sizer changes, and is somewhat dependent on the specific kind of sizer used.
Sizer Flag | Description |
---|---|
wx.TOP | These flags are used to specify which side(s) of the sizer item the border width will apply to. |
wx.BOTTOM | |
wx.LEFT | |
wx.RIGHT | |
wx.ALL | |
wx.EXPAND | The item will be expanded to fill the space assigned to the item. |
wx.SHAPED | The item will be expanded as much as possible while also maintaining its aspect ratio |
wx.FIXED_MINSIZE | If you would rather have a window item stay the size it started with then use FIXED_MINSIZE |
wx.RESERVE_SPACE_EVEN_IF_HIDDEN | Normally Sizers don’t allocate space for hidden windows or other items. This flag overrides this behavior so that sufficient space is allocated for the window even if it isn’t visible. This makes it possible to dynamically show and hide controls without resizing parent dialog, for example. |
wx.ALIGN_CENTER or wx.ALIGN_CENTRE | The ALIGN* flags allow you to specify the alignment of the item within the space allotted to it by the sizer, adjusted for the border if any. |
wx.ALIGN_LEFT | |
wx.ALIGN_RIGHT | |
wx.ALIGN_TOP | |
wx.ALIGN_BOTTOM | |
wx.ALIGN_CENTER_VERTICAL or wx.ALIGN_CENTRE_VERTICAL | |
wx.ALIGN_CENTER_HORIZONTAL or wx.ALIGN_CENTRE_HORIZONTAL |
Let’s start with the simplest case: the alignment flags. These are pretty self-explanatory.
Code | Resulting Image |
---|---|
sizer = wx.BoxSizer(wx.VERTICAL)
# Second button is right aligned
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 0, wx.ALIGN_RIGHT, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
# Second button is center-aligned
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 0, wx.ALIGN_CENTER, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
Next is the wx.EXPAND flag. This is synonymous with wx.GROW.
Code | Resulting Image |
---|---|
sizer = wx.BoxSizer(wx.VERTICAL)
# Second button expands to the whole parent's width
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 0, wx.EXPAND, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
You can see that the first button takes its minimum size, and the second one grows to match it. This affects controls in the opposite manner of the second parameter; wx.EXPAND in a vertical sizer causes horizontal expansion, and in a horizontal sizer it causes vertical expansion.
Next is wx.SHAPED. This flag ensures that the width and height of the object stay proportional to each other. It doesn’t make much sense for buttons, but can be excellent for bitmaps, which would be contorted or clipped if not scaled proportionally.
Code | Resulting Image |
---|---|
sizer = wx.BoxSizer(wx.VERTICAL)
# Second button will scale proportionally
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 1, wx.SHAPED, 0)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
|
Same code as above, with window resized. The width grew dramatically with the height. In fact, it didn’t quite grow vertically the whole way because it couldn’t maintain the correct ratio while doing so. |
Finally, we have the border flags. These only make sense when the border parameter is greater than 0, and describe the sides of the control on which the border should appear. In order to demonstrate this most clearly, we’ll keep the wx.EXPAND flag.
Code | Resulting Image |
---|---|
sizer = wx.BoxSizer(wx.VERTICAL)
# Border size effects
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 0, wx.EXPAND | wx.LEFT, 20)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
# Border size effects
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 20)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
# Border size effects
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 20)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
# Border size effects
sizer.Add(wx.Button(self, -1, "An extremely long button text"), 0, 0, 0)
sizer.Add(wx.Button(self, -1, "Small Button"), 0, wx.EXPAND | wx.ALL, 20)
sizer.SetSizeHints(self)
self.SetSizer(sizer)
|
You can see that the button is offset from the specified edges of the sizer by the number of pixels that we specified in the border parameter.
Hints
Exercises
Starting from the Sizers sample:
Click on the figure for the solution.
Hints
Exercises
Run the Sizers+ sample (without looking at the code!!), and The Widget Inspection Tool will pop up.
Click on the figure for the solution.
This section will briefly show some basic information about graphic device interface objects, which include fonts, colours and bitmaps.
A font is an object which determines the appearance of text, primarily when drawing text to a window or device context. A wx.Font is determined by the following parameters (not all of them have to be specified, of course):
Point size | This is the standard way of referring to text size. |
Family | Supported families are: wx.DEFAULT, wx.DECORATIVE, wx.ROMAN, wx.SCRIPT, wx.SWISS, wx.MODERN. wx.MODERN is a fixed pitch font; the others are either fixed or variable pitch. |
Style | The value can be wx.NORMAL, wx.SLANT or wx.ITALIC. |
Weight | The value can be wx.NORMAL, wx.LIGHT or wx.BOLD. |
Underlining | The value can be True or False. |
Face name | An optional string specifying the actual typeface to be used. If None, a default typeface will chosen based on the family. |
Encoding | The font encoding |
As an example, you can create a font object and assign it to a widget like this:
static_text = wx.StaticText(parent, -1, 'Some text here')
font1 = wx.Font(10, wx.SWISS, wx.ITALIC, wx.NORMAL)
static_text.SetFont(font1)
text_ctrl = wx.TextCtrl(parent, -1, 'Some other text')
# A font can be retrieved from the OS default font and modified
font2 = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
font2.SetPointSize(12)
text_ctrl.SetFont(font2)
A colour is an object representing a combination of Red, Green, Blue and Alpha channel (RGBA) intensity values. Valid RGBA values are in the range 0 to 255.
There are three ways for setting colours. We can create a wx.Colour object, use a predefined colour name or use hex value string:
text_ctrl = wx.TextCtrl(parent, -1, 'Some text')
text_ctrl_SetBackgroundColour(wx.Colour(0, 0, 255))
# OR
text_ctrl_SetBackgroundColour('BLUE')
# OR
text_ctrl.SetBackgroundColour('#0000FF')
As an alternative, you can also use the handy colour database provided in the wx.lib.colourdb module and comprising more than 600 named colours:
import wx
import wx.lib.colourdb
import random
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='Colour database')
# Show the selected colour in this panel
panel = wx.Panel(self)
wx.lib.colourdb.updateColourDB()
# Create a colour list from the colourdb database
colour_list = wx.lib.colourdb.getColourList()
# Get one random colour between those 600 available
random_colour = random.choice(colour_list)
panel.SetBackgroundColour(random_colour)
A bitmap is a collection of bits that form an image. It is a grid of individual dots stored in memory or in a file. Each dot has it’s own colour. When the image is displayed, the computer transfers a bit map into pixels on monitors or ink dots on printers. The quality of a bitmap is determined by the resolution and the color depth of the image. The resolution is the total number of pixels in the image. The color depth is the amount of information in each pixel.
An example construction to create a bitmap in wxPython starting from an existing image:
bmp = wx.Bitmap('wxpython.png', wx.BITMAP_TYPE_PNG)
The same can be done with other file formats, such as GIF, JPG, BMP, TIFF, etc... The full list of supported formats is available in the BitmapType documentation.
When you need to use standard (i.e., platform-provided) bitmaps, you should take a look at wx.ArtProvider, which is a class used to customize the look of wxPython application.
When wxPython needs to display an icon or a bitmap (e.g. in the standard file dialog), it does not use a hard-coded resource but asks wx.ArtProvider for it instead. A number of standard bitmaps are readily available, and in particular the following:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In order to use wx.ArtProvider and retrieve a standard bitmap, you may do something like:
bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
static_bitmap = wx.StaticBitmap(parent, -1, bmp)
Hints
Exercises
Click on the figure for the solution.
We will now explore a few of the most frequently used core controls in wxPython. The actual number of core classes (100) is way too big to explore them all in this tutorial, but this introductory overview should get you started and whet your appetite for more...
Common dialog classes and functions encapsulate commonly-needed dialog box requirements. They are all modal, grabbing the flow of control until the user dismisses the dialog, to make them easy to use within an application.
Some dialogs have both platform-dependent and platform-independent implementations, so that if underlying windowing systems do not provide the required functionality, the generic classes and functions can stand in. For example, under Windows, ColourDialog uses the standard colour selector. There is also an equivalent called GenericColourDialog for other platforms.
wxPython wraps pretty much all the standard dialogs:
wx.FileDialog pops up a file selector box. On Windows and GTK 2.4+, this is the common file selector dialog. Looking at its constructor:
Parameters: |
|
---|
The path (defaultDir) and filename (defaultFile) are distinct elements of a full file pathname.
If defaultDir is “”, the current directory will be used. If defaultFile is “”, no default filename will be supplied. The wildcard parameter determines what files are displayed in the file selector, and file extension supplies a type extension for the required filename.
The most common style bits used are wx.FD_OPEN (for a “file open” dialog) and wx.FD_SAVE (for a “file save” dialog).
wx.FileDialog implements a wildcard filter. Typing a filename containing wildcards (*, ?) in the filename text item, and clicking on Ok, will result in only those files matching the pattern being displayed.
The wildcard may be a specification for multiple types of file with a description for each, such as:
wildcard = "BMP files (*.bmp)|*.bmp|GIF files (*.gif)|*.gif"
As an example, to give the user the possibility to choose one Python file in a folder:
# This is how you pre-establish a file filter so that the dialog
# only shows the extension(s) you want it to.
wildcard = 'Python source (*.py)|*.py'
dlg = wx.FileDialog(None, message="Choose a Python file", defaultDir=os.getcwd(),
defaultFile="", wildcard=wildcard, style=wx.FD_OPEN)
# Show the dialog and retrieve the user response. If it is the OK response,
# process the data.
if dlg.ShowModal() == wx.ID_OK:
# This returns the file that was selected
path = dlg.GetPath()
print(path)
# Destroy the dialog. Don't do this until you are done with it!
# BAD things can happen otherwise!
dlg.Destroy()
This dialog shows a single or multi-line message, plus buttons that can be chosen from OK, Cancel, Yes, and No. Under Windows, an optional icon can be shown, such as an exclamation mark or question mark.
It is mostly used as informational message window. As an example, if you want to inform the user that some process in your GUI has finished running, you may do the following:
dlg = wx.MessageDialog(None, message='Calculations finished', caption='Information',
style=wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
Hints
Exercises
Using the Widgets sample:
Click on the figure for the solution.
In this section we are going to explore a few of the most commonly used widgets – although not the basic ones such as wx.ComboBox or wx.SpinCtrl.
This class represents a notebook control, which manages multiple windows with associated tabs.
To use the class, create a wx.Notebook object and call AddPage or InsertPage, passing a window to be used as the page.
As an example, this is how you may add a tabbed page to your notebook control:
notebook = wx.Notebook(parent)
# Create the page windows as children of the notebook
page = wx.Panel(notebook)
# Add the pages to the notebook with the label to show
# on the tab
notebook.AddPage(page, 'Page 1')
Hints
Exercises
Using the Notebook sample:
Click on the figure for the solution.
This widget enables to split the main area of an application into parts. The user can dynamically resize those parts with the mouse pointer. Such a solution can be seen in mail clients or in burning software. You can split an area vertically or horizontally.
As an example on how to use it:
import wx
class SplitterFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='wx.SplitterWindow')
splitter = wx.SplitterWindow(self, -1)
panel1 = wx.Panel(splitter, -1)
static = wx.StaticText(panel1, -1, 'Hello World', pos=(100, 100))
panel1.SetBackgroundColour(wx.LIGHT_GREY)
panel2 = wx.Panel(splitter, -1)
panel2.SetBackgroundColour(wx.WHITE)
splitter.SplitVertically(panel1, panel2)
self.Centre()
Hints
Exercises
Using the Splitter sample:
Click on the figure for the solution.
A tree control presents information as a hierarchy, with items that may be expanded to show further items. It is implemented natively on Windows and it uses a generic implementation on GTK and Mac OS, although newer versions of wxPython (the 2.9 ones) provide a native implementation in DataViewTreeCtrl.
There is also a pure-Python implementation (with many more functionalities) in wx.lib, termed CustomTreeCtrl.
A simple application showing the usage of wx.TreeCtrl is as follows:
import wx
class TreeFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='wx.TreeCtrl')
tree_ctrl = wx.TreeCtrl(self, -1, style=wx.TR_DEFAULT_STYLE)
# Add the tree root
root = tree_ctrl.AddRoot('Root item')
for i in range(10):
tree_ctrl.AppendItem(root, 'Item %d'%(i+1))
tree_ctrl.ExpandAll()
self.Centre()
Hints
Exercises
Using the TreeCtrl sample and the Python keyword module:
Click on the figure for the solution.
In this section, we are going to look at few generalities about custom controls and how to draw custom objects on some wxPython windows. Unfortunately the entire “owner-draw” subject is way too big to be covered during a short tutorial, but this section should at least get you started and whet your appetite for more.
(If you want to hear more details, please feel free to contact me at any time during the PythonBrasil8 conference).
A DC is a device context onto which graphics and text can be drawn. It is intended to represent different output devices and offers a common abstract API for drawing on any of them.
DCs have many drawing primitives:
And they work with GDI objects:
Some device contexts are created temporarily in order to draw on a window. This is true for some of the device contexts available for wxPython:
Let’s focus on the PaintDC, which is one of the most commonly used. To use this device context, we want to bind a paint event for a window to an event handler, which will be responsible for drawing (almost) anything we want onto our window:
Documentation
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title)
# Bind a "paint" event for the frame to the
# "OnPaint" method
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Show()
def OnPaint(self, event):
dc = wx.PaintDC(self)
# Set a red brush to draw a rectangle
dc.SetBrush(wx.RED_BRUSH)
dc.DrawRectangle(10, 10, 50, 50)
The PaintEvent is triggered every time the window is redrawn, so we can be sure that our red rectangle will always be drawn when the operating system wants to “refresh” the content of our window.
Similar things can be done using other graphical primitives, like DrawPoint:
def OnPaint(self, event):
dc = wx.PaintDC(self)
# Use a red pen to draw the points
dc.SetPen(wx.Pen('RED'))
# Get the size of the area inside the main window
w, h = self.GetClientSize()
# Draw a sequence of points along the mid line
for x in range(1, w, 3):
dc.DrawPoint(x, h/2)
Documentation
Or drawing text strings onto our window by using DrawText:
def OnPaint(self, event):
dc = wx.PaintDC(self)
# Use a big font for the text...
font = wx.Font(20, wx.SWISS, wx.NORMAL, wx.BOLD)
# Inform the DC we want to use that font
dc.SetFont(font)
# Draw our text onto the DC
dc.DrawText('Hello World', 10, 10)
Hints
Exercises
Modify the DC sample such that:
Click on the figure for the solution.