Quick Guide to wxWidgets and wxPython

Introduction

wxWidgets, formerly known as wxWindows, is an open-source, cross-platform framework in C++ which uses the target OS' native widgets to achieve native look and feel. wxPython is a thin layer above the wxWidgets framework, so loads faster than Tkinter. It also offers a rich set of widgets, and seems easier to use, at the expense of having to install an extra package while Tkinter is part of the Python package. The name wxWidgets refers to the two original platforms—“w” for Microsoft Windows and “x” for Unix X server.

Phoenix/wxPython 4 is a rewrite of wxPython for Python3. Code for older versions of Python/wxPython will probably need to be edited accordingly. The most up-to-date source of information is here: https://docs.wxpython.org/

Since wxPython relies on Python, do not upgrade the latter unless you're sure it won't break the former.

As of 2024, besides the the wiki and the demo scripts (cd demo; pythonw demo.py), Michael Driscoll's "Creating GUI Applications with wxPython", published in 2020, seems the only book worth recommending today as it matches the "Project Phoenix" rewrite of wxPython, although it mostly shows samples instead of really explaining the toolkit — "wxPython in Action" was published in 2006 so the code won't work with Python3 and Phoenix; "wxPython Application Development Cookbook" and "wxPython Recipes" have quite negative user comments.

wxGlade is a GUI builder for wxWidget — the (much) bigger ZIP is a ready-to-use package while the smaller assumes Python and wxPython are pre-installed.

Setup

If needed, start by installing Python. If you work under Windows, you can either install the standard release of Python, or 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.

To install wxPython, simply, run "pip install wxpython"; It contains wxWidgets, so you don't need to download both. To check which version of Python is installed, open a terminal window, and type "python -V", and to check which version of wxPython is installed, run "python -c "import wx;print(wx.__version__)" "

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 Thonny (to use an existing Python setup, select it in the drop-down list in Tools > Options > Interpreter; (4.1.4) doesn't scroll in-place with CTRL+up/down), Wing IDE Pro (commercial), PyCharm (free/commercial; The installer of the free version is already over 450MB), Visual Studio Code (open source), PyScripter (open source; Written in Delphi).
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.

Development with wxPython

Hello, world!

Launch your favorite text editor, and copy/paste the following code into eg. basic.py:

#Ignore "Unable to set default locale: 'unsupported locale setting' "
import wx

class MyFrame(wx.Frame):
  def __init__(self):
    super().__init__(None, title='Hello World')
    self.Show()

#Needed since wxPython does not have an automatic macro for creating a main starting point
if __name__ == '__main__':
  app = wx.App()
  frame = MyFrame()
  app.MainLoop()

The script can be run through either "python myscript.py", "pythonw myscript.py", or just "myscript.py" or "myscript.pyw" if the OS can locate the Python interpreter.

Vocabulary

As shown above, a wxPython application requires two main objects: An application object and a top-level window object. The primary purpose of the application object is to manage the main event loop behind the scenes.

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.”"

Events

How an app handles an event.

import wx
 
class MyPanel(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent)
        button = wx.Button(self, label='Press Me')
        button.Bind(wx.EVT_BUTTON, self.on_button_press)
 
    def on_button_press(self, event):
        print('You pressed the button')
 
class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title='Hello World')
        panel = MyPanel(self)
        self.Show()
 
if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

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 (those are the most common):

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

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

https://docs.wxpython.org/wx.adv.SashWindow.html

Showing an OK dialog box

import wx
 
app = wx.App()
wx.MessageBox("Body", "Title", wx.OK | wx.ICON_INFORMATION)

Prompting for a path

if 'app' not in locals():
    app = wx.App(None)
 
dlg = wx.DirDialog (None, 'Select Directory', '', wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST)
if dlg.ShowModal() == wx.ID_OK:
        DirName = dlg.GetPath()
else:
        DirName = None
dlg.Destroy()
print(DirName)

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 panel 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()

Distributing an applications

Several tools (py2exe, py2app, PyInstaller, cx_Freeze, bbfreeze, Nuitka) are available to compile a script so that it runs on a computer without the user having to install Python and dependencies. They can even compile all the files into a single binary and avoid forcing the user to run an installer (eg. NSIS, Inno Installer).

Here's how to use PyInstaller: pyinstaller myscript.pyw --onefile --noconsole

Reading notes "wxPython in Action"

Note: This book came out in 2006; There might be parts that are no longer relevant and code that won't run.

Two objects are required in all wxPython applications.: The application object, which manages the event loop and oversees the application lifecycle; The top-level window, which is the focal point of user interaction with your program.

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__
"""
 
import wx #always import wx before anything else from wxPython
 
# 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 frame cannot be created before the application object; For this reason, it's recommended to create the top-level frame in the OnInit() method—doing so guarantees that the application already exists.

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()

To use sys.stdout/sys.stderr: When your application object is created you can decide to have wxPython take control of the standard streams and redirect the output to a window instead. See the example in section "2.3.1 Redirecting output".

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 keep 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

Q&A

import wx: Is "wx." required when calling methods/properties?

Why import sub-modules after main module?

import wx # Always import wx before
from wx import xrc # any other wxPython packages,
from wx import html # just to be on the safe side.

Reading notes "Creating GUI Applications with wxPython"

Phoenix was the code name for the Python 3 port of wxPython. The previous versions of wxPython are not completely compatible with wxPython 4.

The wx.Frame is the window object that contains all the other widgets. The Panel is a container that also enables the ability to tab between widgets. You can use Panels to group widgets as well.

A most basic app:

import wx
 
app = wx.App()
frame = wx.Frame(None, title='Hello World') #Frame required at least a parent
frame.Show()
 
app.MainLoop()

To make code more modular, most code is put in classes: The frame class for widgets related to the frame, and the panel class for those grouped into a panel.

A panel should have a parent, which in this case is a Frame. You only want one panel as the sole widget for a frame. The panel will automatically expand to fill the frame as well if it is the only child widget of the frame. wx.Panel widgets enable tabbing between widgets on Windows. So if you want to be able to tab through the widgets in a form you have created, you are required to have a panel as their parent.

import wx
 
class MyPanel(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent)
        button = wx.Button(self, label='Press Me')
class MyFrame(wx.Frame):
    def __init__(self):
        #Python 3 recommends using super() when working with classes; Its primary purpose in wxPython is used for         #referring to the parent class without actually naming it
        super().__init__(None, title='Hello World')
        panel = MyPanel(self)
        self.Show()
 
if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Here's how to add a push button:

import wx
 
class MyPanel(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent)
        button = wx.Button(self, label='Press Me')
        button.Bind(wx.EVT_BUTTON, self.on_button_press)
 
    def on_button_press(self, event):
        print('You pressed the button')
 
class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title='Hello World')
        panel = MyPanel(self)
        self.Show()
 
if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

wxPython supports both absolute positioning of widgets and relative positioning of widgets. Relative positioning of widgets requires the use of special container objects called Sizers or Layouts. A sizer allows you to resize your application and have the widgets resize along with it. It is always recommended that you use sizers as they will make your application much nicer to use on multiple displays and screen sizes.

The wxPython toolkit has several sizers:

Adding the button to the sizer:

import wx
 
class MyPanel(wx.Panel):
    def __init__(self, parent):
        super().__init__(parent)
        button = wx.Button(self, label='Press Me')
        main_sizer = wx.BoxSizer(wx.HORIZONTAL)
        main_sizer.Add(button, proportion=0,flag=wx.ALL | wx.CENTER,border=5)
        self.SetSizer(main_sizer)
 
class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title='Hello World')
        panel = MyPanel(self)
        self.Show()
 
if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Notes:

The book goes on showing different how to write different applications (an image viewer, a database viewer+editor, a calculator, etc.)

Reading notes "wxPython Recipes A Problem - Solution Approach — Mike Driscoll"

Reading notes "wxPython Application Development Cookbook — Cody Precord"

Q&A

PyQt vs. wxPython

https://www.pythonguis.com/faq/which-python-gui-library/

"Unable to set default locale: 'unsupported locale setting'"

https://docs.wxpython.org/MigrationGuide.html#possible-locale-mismatch-on-windows

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.

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