Quick Guide to wxWidgets and wxPython

Introduction

wxWidgets, formerly known as wxWindows, is an open-source, cross-platform framework which uses the target OS' native widgets to achieve native look and feel. wxPython is a thin layer above the wxWidgets framework.

wxApp -> wxFrame -> wxPanel -> wxSizer -> wxControl

Setup

If you work under Windows, the best choice is to install ActiveState's version of Python: It includes Python as available on www.python.org, Windows-specific add-on's like access to the Win32 API and COM, along with a few other items like BerkeleyDB.The Enthought version includes a lot of stuff that are probably not useful to most developers, but you might need those. FWIW, the so-called "Python IDE" that comes with ActiveState is just a graphical command-line interface to Python; Nothing like rich IDE's like Delphi or VB. You may need to add the path to the Python folder to the PATH environment variable.

Once Python is installed, install wxPython. It contains wxWidgets, so you don't need to download both. Make sure you download the wxPython package that matches the version of Python that you installed previously, eg. 2.4.x or 2.5.x (the wxPython installer will tell you if it can't find the right version anyway.) As an option, you can download the documentation and demos from the site. In Windows Explorer, double-click on "C:\Program Files\wxPython2.6 Docs and Demos\demo\demo.py" to run the demo.

If you choose to use UltraEdit to work with Python and want to add support for syntax highlighting, wordfiles are available on the site for various versions of Python. As alternate IDE's built for Python, take at look at Stani's Python Editor (SPE; It includes the wxGlade GUI designer) or ActiveState's Komodo.

Development with wxPython

Up and running in 5mn

This will show you how to launch your very first wxPython script in Windows:

  1. Install the latest ActivePython and wxPython (it includes wxWidgets)
  2. Launch your favorite text editor, and copy/paste the following code into eg. 01_wxpython.py:

    import wx

    app = wx.PySimpleApp()
    frame = wx.Frame(None, wx.ID_ANY, "Hello World")
    frame.Show(True)
    app.MainLoop()

  3. Open a terminal window, and type "01_wxpython.py"

Vocabulary

Window? Frame?

"When most people look at this running program, they see something they would call a “window.” However, wxPython does not call this a window. It calls this a “frame.” In wxPython,“window” is the generic term for any object that displays on the screen (what other toolkits might call a “widget”). So, a wxPython programmer will often refer to objects such as buttons or text boxes as “windows.” This may seem confusing, but the usage dates to the earliest days of the original C++ toolkit, and it’s unlikely to change now. In this book, we’ll try to avoid the use of window as a generic term, because it’s confusing and also because it’s the name of a big product from a major corporation. We’ll use widget as the generic term. When we’re specifically referring to the operating system of similar name, we’ll do it with a capital “W.”"

Frame vs. Panel

Use a frame when you need a window for your application; Use a panel (within that frame) to place other widgets onto.  Don't place (most) widgets right onto the frame itself; there are some problems with that. You can and often will use multiple panels within the same frame.

Sizer

As an easy way to lay down widgets into a frame, use the wx*Sizer classes:

Notebook

wxPython-speak for what is otherwise known in the Windows world as the tab widget, ie. multiple sub-windows on top of each other.

Sash Window

??

Plain, empty frame

First, the obligatory "Hello, world!":

from wxPython.wx import wxPySimpleApp, wxFrame
 
app = wxPySimpleApp()
frame = wxFrame(None, -1, "Hello World")
frame.Show(1)
app.MainLoop()

Basic frame with a label + button

This can be used to launch a script when the user clicks on the button, and display information in the label:

 

Text editor

Next, a basic editor (from wxPython for newbies):

import sys, os
from   wxPython.wx import *
 
class main_window(wxFrame):
    def __init__(self, parent, id, title):
        wxFrame.__init__(self, parent, -1, title, size = (200, 100),
                                             style=wxDEFAULT_FRAME_STYLE|wxNO_FULL_REPAINT_ON_RESIZE)
        self.control = wxTextCtrl(self, -1, style=wxTE_MULTILINE)
        self.Show(true)
 
class App(wxApp):
    def OnInit(self):
        frame = main_window(None, -1, "wxPython: (A Demonstration)")
        self.SetTopWindow(frame)
        return true
app = App(0)
app.MainLoop()

Frame with menu bar and dialog box

Here's how to add a menu bar, and display a dialogbox when choosing an item:

from wxPython.wx import *
 
ID_ABOUT = 101
ID_EXIT  = 102
 
class MyFrame(wxFrame):
        def __init__(self, parent, ID, title):
                wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(200, 150))
                self.CreateStatusBar()
                self.SetStatusText("This is the statusbar")
                menu = wxMenu()
                menu.Append(ID_ABOUT, "&About", "More information about this program")
                menu.AppendSeparator()
                menu.Append(ID_EXIT, "E&xit", "Terminate the program")
                menuBar = wxMenuBar()
                menuBar.Append(menu, "&File");
                self.SetMenuBar(menuBar)
                
                EVT_MENU(self, ID_ABOUT, self.OnAbout)
                EVT_MENU(self, ID_EXIT,  self.TimeToQuit)
 
        def OnAbout(self, event):
                dlg = wxMessageDialog(self, "This sample program shows off\n"
                                      "frames, menus, statusbars, and this\n"
                                      "message dialog.",
                                      "About Me", wxOK | wxICON_INFORMATION)
                dlg.ShowModal()
                dlg.Destroy()
 
        def TimeToQuit(self, event):
                self.Close(true)
 
class MyApp(wxApp):
        def OnInit(self):
                frame = MyFrame(NULL, -1, "Hello from wxPython")
                frame.Show(true)
                self.SetTopWindow(frame)
                return true
 
app = MyApp(0)
app.MainLoop()

Frame window with plane and widgets

Here, we'll build a filled window that contains a plane, ie. not just an empty frame, and add widgets on the plane:

from wxPython.wx import *
import sys
import urllib
import re
 
ID_OKBT=100
 
class form(wxPanel):
        def __init__(self, parent, id):
                wxPanel.__init__(self, parent, id)
                self.current = wxStaticText(self, -1, "Before",wxPoint(5, 5))
                
                self.button =wxButton(self, ID_OKBT, "Click me", wxPoint(100, 5))
                EVT_BUTTON(self, ID_OKBT, self.OnClick)
        
        def OnClick(self,event):
                self.current.SetLabel('After')
                wxYield()
                                
app = wxPySimpleApp()
frame = wxFrame(None, -1, "Some app")
form(frame,-1)
frame.Show(1)
app.MainLoop()

Showing a standard dialog box

d= wxMessageDialog( self, " A sample editor \nin wxPython","About Sample Editor", wxOK)
d.ShowModal()
d.Destroy()

Reading notes "wxPython in Action"

What we generally call a window in Windows is called a frame in wxWidgets. In wxWidgets, windows refer to any widget within a frame.

A bare frame:

"""This is the module's docstring. It can be viewed by importing the module, and running
print mymodule.__doc__
"""
 
# Always import wx before other wxPython packages
import wx
 
# Every wxPython program must have one application object and at least one frame object
class App(wx.App):
    def OnInit(self):
        frame = wx.Frame(parent=None, title='Bare')
        frame.Show()
        return True
 
app = App()
app.MainLoop()

A more real-life structure:

import wx
 
class Frame(wx.Frame):
        pass
 
class App(wx.App):
        def OnInit(self):
                self.frame = Frame(parent=None, title='Spare')
                self.frame.Show()
                #an application can only have one top window at a time.
                self.SetTopWindow(self.frame)
                return True
 
if __name__ == '__main__':
    #To redirect output to a file, use this instead:
    #app = App(True, "output.log")
    app = App()
    app.MainLoop()

If the application is so simple as to only need a single frame, there's an easier way to create an application:

class PySimpleApp(wx.App):
        def __init__(self, redirect=False, filename=None, useBestVisual=False, clearSigInt=True):
                wx.App.__init__(self, redirect, filename, useBestVisual, clearSigInt)
 
        def OnInit(self):
                return True
 
if __name__ == '__main__':
        app = wx.PySimpleApp()
        frame = MyNewFrame(None)
        frame.Show(True)
        app.MainLoop()

A frame cannot be created before the application object. The most important use of ID numbers in wxPython is to create a unique relationship between an event that happens to a specific object and a function which is called in response to that event.

In real-life applications, widgets such as push-buttons are not created directly in a frame, but rather in a panel, which acts as a container and is itself created in a frame. A panel usually overlays the entire frame, and helps keeping the widgets separate from the toolbar and status bar. When a frame is created with just a single child window, that child window (typically, a panel) is automatically resized to fill the client area of the frame.

Use a sizer to avoid having to specify the position and size of each widget, including when the user resizes the frame/panel.

Common dialogs are available. Here's an example:

dlg = wx.MessageDialog(None, 'Is this the coolest thing ever!', 'MessageDialog', wx.YES_NO | wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()

If you just need some basic, text input from the user:

dlg = wx.TextEntryDialog(None, "Who is buried in Grant's tomb?",'A Question', 'Cary Grant')
if dlg.ShowModal() == wx.ID_OK:
    response = dlg.GetValue()

If you want to have the user pick an item in a limited list:

dlg = wx.SingleChoiceDialog(None, 'What version of Python are you using?', 'Single Choice', ['1.5.2', '2.0', '2.1.3', '2.2', '2.3.1'],
if dlg.ShowModal() == wx.ID_OK:
    response = dlg.GetStringSelection()

Events are represented as instances of the wx.Event class and its subclasses, such as wx.CommandEvent and wx.MouseEvent. An event handler is a written function or method that is called in response to an event. Also called a handler function or handler method. An event binder is a wxPython object that encapsulates the relationship between a specific widget, a specific event type, and an event handler. In order to be invoked, all event handlers must be registered with an event binder.

PyCrust is a graphical shell program, written in wxPython, that you can use to help analyze your wxPython programs. PyCrust is part of a larger Py package that includes additional programs with related functionality including PyFilling, PyAlaMode, PyAlaCarte, and PyShell.

The key to a successful MVC design is not in making sure that every object knows about every other object. Instead, a successful MVC program explicitly hides knowledge about one part of the program from the other parts. The goal is for the systems to interact minimally, and over a well-defined set of methods. In particular, the Model component should be completely isolated from the View and Controller.

Because both refactoring and the use of an MVC design pattern tend to break your program into smaller pieces, it is easier for you to write specific unit tests targeting individual parts of your program. Since version 2.1, Python has been distributed with the unittest module. The unittest module implements a test framework called PyUnit.

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.

The primary wx.Frame class has several different frame style bits which can change its appearance. In addition, wxPython offers miniframes, and frames that implement the Multiple Document Interface (MDI). Frames can be split into sections using splitter bars, and can encompass panels larger than the frame itself using scrollbars.

A panel is an instance of the class wx.Panel, and is a simple container for other widgets with little functionality of its own. You should almost always use a wx.Panel as the top-level subwidget of your frame. For one thing, the extra level can allow greater code reuse, as the same panel and layout could be used in more than one frame. Using a wx.Panel gives you some of the functionality of a dialog box within the frame. This functionality manifests itself in a couple of ways. One is simply that wx.Panel instances have a different default background color under MS Windows operating systems—white, instead of gray. Secondly, panels can have a default item that is automatically activated when the Enter key is pressed, and panels respond to keyboard events to tab through the items or select the default item in much the same way that a dialog does.

Here's how to create an MDI parent/child interface:

class MDIFrame(wx.MDIParentFrame):
        def __init__(self):
                wx.MDIParentFrame.__init__(self, None, -1, "MDI Parent", size=(600,400))
 
                menu = wx.Menu()
                menu.Append(5000, "&New Window")
                menu.Append(5001, "E&xit")
                menubar = wx.MenuBar()
                menubar.Append(menu, "&File")
                self.SetMenuBar(menubar)
 
                self.Bind(wx.EVT_MENU, self.OnNewWindow, id=5000)
                self.Bind(wx.EVT_MENU, self.OnExit, id=5001)
 
        def OnExit(self, evt):
                self.Close(True)
 
        def OnNewWindow(self, evt):
                win = wx.MDIChildFrame(self, -1, "Child Window")
                win.Show(True)

A splitter window is a particular kind of container widget that manages exactly two sub-windows. The two sub-windows can be stacked horizontally or next to each other left and right. In between the two sub-windows is a sash, which is a movable border that changes the size of the two sub-windows. Splitter windows are often used for sidebars to the main window (i.e., a browser).

In wxPython, a wizard is a series of pages controlled by an instance of the class wx.wizard.Wizard. The wizard instance manages the events that take the user through the pages. The pages themselves are instances of either the class wx.wizard.WizardPageSimple or wx.wizard.WizardPage. In both cases, they are merely wx.Panel instances with the additional logic needed to manage the page chain.

A validator is a special wxPython object that simplifies managing data in a dialog. A validator is attached to a specific widget in your system.

The recommended way to deal with complicated layout these days is by using a sizer. A sizer is an automated algorithm for laying out a group of widgets. A sizer is attached to a container, usually a frame or panel. Subwidgets that are created within a parent container must be separately added to the sizer. When the sizer is attached to the container, it then manages the layout of the children contained inside it. The most flexible sizers, the grid bag and box, will be able to do nearly everything you’ll want them to. A wxPython sizer is an object whose sole purpose is to manage the layout of a set of widgets within a container. The sizer is not a container or a widget itself. It is just the representation of an algorithm for laying out a screen.

Predefined sizers:

p.325

wxWidgets in C/C++

Read Which is the best compiler to use with wxWindows 2? to check which compiler you'd rather work with if you are just getting started. A list of C compilers can be found here.

Q&A

How to upgrade wxPython?

Just run the Windows installer, which will take care of uninstalling the current version, if need be.

How are the frame and event handler connected?

In a Frame, what's the difference between eg. button vs. self.button?

Both widgets are located in the same frame. Why use self.button?

class Frame(wx.Frame):
        def __init__(self):
                wx.Frame.__init__(self, None, -1, 'Static Text Example',size=(400, 300))
 
                panel = wx.Panel(self, -1)
 
                basicLabel = wx.StaticText(panel, -1, "This is an example of static text", (100, 100))
 
                self.button = wx.Button(panel, -1, "Hello", pos=(50, 200))
                self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)
                self.button.SetDefault()

Under Windows, I don't want to see the empty DOS box in the background

Rename the script from .py to .pyw.

Any tool to reformat source code?

It's a pain to have to TAB things right when copy/pasting code from the Net.

What is a sizer?

"The sizers are a basic tool in wxWidgets to get layouts to be mostly portable to other platforms where the widgets may be of different size. They are less important if you plan to develop for only one platform but its useful to get used to them as they can also aid when adding/removing elements from the design."

Resources

Help

Literature

wxPython

wxWidgets

Tools