Python and wxWidgets – supplementary information
Introduction
Problems can arise later in GF2 when students specialises in the graphics,
because
- the code uses several new concepts
- GUI students might become isolated from the rest of the team
- there’s python2/python3, several versions of xwWidgets, and versions on different platforms. Finding the appropriate documentation can be tricky
The tutorials suggested by wxwidgets.org are a good place to start. Here are some hints and tips –
About xwWidgets
GUI code is structurally rather different to code students might have written before. It’s reactive – at any time a window can change size or a button be pressed, and the program has to respond. A xwWidgets program has
- Widgets (buttons, windows, etc). These have parents – they form a hierarchy. The top window is special.
- Events (triggering actions – keypresses, window resizing, mouse clicks, etc).
When an event happens, the program might want to react to it. You can
arrange for particular events in certain widgets to cause a routine to run.
These routines are referred to as “callbacks” or “handlers”. - Sizers. These are routines that manage the size and position of objects for
you. If for instance you want the objects in a window to all be in a row
filling the available width, you create a sizer with these features (it’s
a one-liner), associate it with the window, and it will do the rest.
Problems can arise if a sizer is asked to control windows that don’t all
have the same parent.
wxWidgets code is O-O (object-orientated). This and Callbacks can lead to scoping issues (things not being available to other things), so beware. Note also that when you read about a specialised object, the documentation might not tell you about the member functions available from the parent objects.
Examples
Here’s a simple program
import wx app = wx.App() window = wx.Frame(None, title = "wxPython Frame", size = (300,200)) panel = wx.Panel(window) label = wx.StaticText(panel, label = "Hello World", pos = (100,50)) window.Show(True) app.MainLoop()
This creates a Frame, then a Panel inside the Frame, then some text in the panel. The final line puts the program into a loop where it waits for things to happen. Users can resize the window but not much else. A frame can have a title bar, and can optionally contain a menu bar, toolbar and status bar; 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. A frame can contain any window that is not a frame or dialog.
wxWidgets has many off-the-shelf facilities to save you rewriting standard code. Try this
import wx app = wx.App() frame = wx.Frame(None, -1, 'win.py') frame.SetDimensions(0,0,200,50) wildcard = "Python source (*.py|*.py" dialog = wx.FileDialog(frame, "Choose a file", "", "", wildcard, wx.FD_OPEN| wx.FD_FILE_MUST_EXIST) if dialog.ShowModal() == wx.ID_OK: print (dialog.GetPath()) dialog.Destroy()
If you select a *.py file and click on the Open button, the filename will be printed on the command line.
Note that you’ll get a “Call to deprecated item. Use SetSize instead.” warning when you run this – a common consequence of wxWidgets documentation not matching the version you’re using. If you replace “frame.SetDimensions(0,0,200,50)” by “frame.SetSize(0,0,200,50)” the message goes away.
The following example shows how to create a panel which has scrollbars only when they’re needed (press the “Make a dummy” button a few times).
import wx import wx.lib.scrolledpanel as scrolled class Gui(wx.Frame): def __init__(self, title): """Initialise widgets and layout.""" super().__init__(parent=None, title=title, size=(800, 600)) # Configure the widgets self.label = wx.StaticText(self, label = "Hello World") self.panel =scrolled.ScrolledPanel(self, size = wx.Size(50,100),style = wx.SUNKEN_BORDER) self.panel.SetAutoLayout(1) self.panel.SetupScrolling() self.run_button = wx.Button(self.panel, wx.ID_ANY, "Make a dummy") self.run_button.Bind(wx.EVT_BUTTON, self.on_run_button) # Configure sizers for layout main_sizer = wx.BoxSizer(wx.HORIZONTAL) self.side_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(self.label, 1, wx.ALL, 5) main_sizer.Add(self.panel, 1, wx.ALL, 5) self.side_sizer.Add(self.run_button, 1, wx.ALL, 5) self.panel.SetSizer(self.side_sizer) self.SetSizeHints(600, 600) self.SetSizer(main_sizer) def on_run_button(self, event): """Handle the event when the user clicks the run button.""" text = "Run button pressed." self.another = wx.Button(self.panel, wx.ID_ANY, "Dummy") self.side_sizer.Add(self.another, 1, wx.ALL, 5) self.Layout() app = wx.App() gui = Gui("Scroll bars") gui.Show(True) app.MainLoop()
Troubleshooting
- Check that your widgets have the right parents and that the sizers are associated with the right objects. Draw a diagram of nested rectangles!
- Reduce the code to the minimum required to reproduce the problem.
Sizers
Sizers have a member function Add
that lets you put an object under the control of the sizer. So for example if you want sizer1
to control the position of panel1, you can do
sizer1.Add(panel1)
You can also put a sizer under the control of another sizer –
sizer1.Add(sizer2)
If sizer2
is controlling the contents of panel1
, these 2 commands are equivalent.
To get a sizer ( sizer1
, say) to control the contents of a window (win1
, say), do
win1.SetSizer(sizer1)
It’s tidier (though not necessary) for each sizer to control the contents of a dedicated panel which is the parent of all the items that the sizer manages.
FAQ
- On the Linux system this message appears in the terminal window sometimes. How to I get rid of it?
GLib-GIO-Message: Using the ‘memory’ GSettings backend. Your settings will not be saved or shared with other applications.
Useexport GIO_EXTRA_MODULES=/usr/lib64/gio/modules/
before you run your program. Put it in your.bashrc
file so you don’t need to re-type it. - What does this mean? I get it each time the window is redrawn
Gtk-WARNING **: Negative content height -9 (allocation 1, extents 5×5) while allocating gadget (node button, owner GtkButton)
Maybe the warning means that there’s not enough vertical space to fit the buttons in properly. First try making the window/panel bigger - What’s a segmentation fault?
A segmentation fault can happen when a program tries to access memory that it’s not allowed to (going off the end of an array; trying to access system-only memory etc). Python can’t really do this directly (though destroying an object then trying somehow to use it may provoke the problem), but Python can call code that’s been written in C/C++, and C/C++ can easily cause segmentation errors. The segmentation error may be your fault (your python code may be asking C/C++ code to do something naughty) or the C/C++ code might be buggy (in which case you’ll need to find a work-around).
Here, FYI, is a program that might cause a segmentation fault#!/usr/bin/env python3 # -*- coding: utf-8 -*- import wx class StatusBar(wx.Panel): def __init__(self): wx.Panel.__init__(self) def set_status(self, status): text = wx.StaticText(parent=self, label=status) class Gui(wx.Frame): def __init__(self, title): """Initialise widgets and layout.""" super().__init__(parent=None, title=title, size=(800, 600)) status_bar = StatusBar() status_bar.set_status('status') app = wx.App() gui = Gui('scratch') gui.Show(True) app.MainLoop()