The wxPython Linux Tutorial

Table of Contents:

Foreword

The purpose of this tutorial is to get you started with the wxPython toolkit, from the basics to the advanced topics. It has lots of code examples, not much talking. After that, you will be able to dig in yourself.

There are three decent toolkits for the python programming language:

Note that this tutorial is done on Linux. Some scripts do not work correctly on windows.

Icons used in this tutorial: icons.tgz Images used in this tutorial: images.tgz

jan bodnar 2005 - 2007

status update. (april 2007) All my work on wxPython tutorial has been moved to my website http://www.zetcode.com/wxpython here I shall not add any more examples. If I find myself some time, I will do some polishing.

wxPython API

wxPython API is a set of functions and widgets. Widgets are essential building blocks of a GUI application. Under Windows widgets are calles controls. We can roughly divide programmers into two groups. They code applications or libraries. In our case, wxPython is a library that is used by application programmers to code applications. Technically, wxPython is a wrapper over a C++ GUI API called wxWidgets. So it is not a native API. e.g. not written directly in Python. The only native GUI library for an interpreted language that I know is Java's Swing library.

In wxPython we have lot's of widgets. These can be divided into some logical groups.

Base Widgets

These widgets provide basic functionality for derived widgets. They are usually not used directly.

Top level Widgets

These widgets exist independently of each other.

Containers

Containers contain other widgets. These widgets are called children.

Dynamic Widgets

These widgets can be edited by users.

Static Widgets

These widgets display informatin. They cannot be edited by user.

Other Widgets

These widgets implement statusbar, toolbar and menubar in an application.

First Steps

We start with a simple example.

   1 #!/usr/bin/python
   2 
   3 # simple.py
   4 
   5 import wx
   6 
   7 app = wx.App()
   8 
   9 frame = wx.Frame(None, -1, 'simple.py')
  10 frame.Show()
  11 
  12 app.MainLoop()

In every wxPython application, we must import the wx library.

import wx

An application object is created by initiating class wx.App.

app = wx.App()

We create a frame widget. The window pops up only if we call Show() method on a widget.

frame = wx.Frame(None, -1, "simple.py")
frame.Show()

The last line enters a 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.

app.MainLoop()

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.

simple.png

Figure: simple.py

wx.Window

wx.Window is a base class out of which many widgets inherit. For instance, the wx.Frame widget inherits from wx.Window. Technically it means that we can use wx.Window's methods for all descendants. We will introduce here some of it's methods.

   1 #!/usr/bin/python
   2 
   3 # simple2.py
   4 
   5 import wx
   6 
   7 app = wx.App()
   8 
   9 frame = wx.Frame(None, -1, '')
  10 frame.SetToolTip(wx.ToolTip('This is a frame'))
  11 frame.SetCursor(wx.StockCursor(wx.CURSOR_MAGNIFIER))
  12 frame.SetPosition(wx.Point(0,0))
  13 frame.SetSize(wx.Size(300,250))
  14 frame.SetTitle('simple2.py')
  15 frame.Show()
  16 
  17 app.MainLoop()

We create a 'This is a frame' tooltip. The cursor is set to a magnifier cursor. Possible cursor id's: are listed below We position the window to the upper left corner and size it to 300x250 pixels. Title is set to 'simple2.py'.

wx.Frame

wx.Frame is a container widget. It means that it can contain other widgets. It has the following constructor:

wx.Frame(wx.Window parent, id, string title,
         wx.Point pos = wx.DefaultPosition, wx.Size size = wx.DefaultSize,
         style = wx.DEFAULT_FRAME_STYLE, string name = 'frame')

A constructor is a special kind of a function. It is called when an object is created. For us it is only important that when we want to create a new widget, we simply call it's constructor. Python enables parameters with default values. So the only obligatory parameters in wx.Frame are parent, id and title. If you specify all values of the parameters in order, you don't need to specify the parameter names. For example you want to create a wx.Frame widget, which has no parent, it's identifier is 100, the title is 'Title', the position is (100,50) and the size is (100,100).

frame = wx.Frame(None, 100, 'Title', wx.Point(100,50), wx.Size(100,100))

Here we have omitted the pos parameter. So we must provide explicitly the size parameter.

frame = wx.Frame(None, 100, 'Title', size = wx.Size(100,100))

In the following example we will use other useful features.

   1 #!/usr/bin/python
   2 
   3 # icon.py
   4 
   5 import wx
   6 
   7 def main():
   8     app = wx.App()
   9 
  10     frame = wx.Frame(None, title='Icon', pos=(350,300))
  11     frame.SetIcon(wx.Icon('tipi.ico', wx.BITMAP_TYPE_ICO))
  12     frame.Center()
  13     frame.Show()
  14     app.MainLoop()
  15 
  16 if __name__ == '__main__':
  17     main()

Icon's name is Tipi.ico. The icon is located in current directory. First parameter of an icon's constructor is the file name. Second parameter specifies the file type.

As you have noticed, the structure of our application has changed. This is a standard in Python programs. In Python programs name is a special variable. More complicated programs consist of several files. There is usually only one file, which launches the application. For this file, Python sets the name variable to 'main'. This happens when you launch an application from the command line or when you click on the file. So when you click on the icon.py file or you launch it from the command line, the name variable equals main. Then function main() is called.

icon.png

Figure: icon.py

wx.MenuBar

To set up a menubar in your wxPython application is pretty simple. We will discuss adding menus to menubar, adding submenus to existing menus. Each menu consists of menuitems. Menuitems can be normal items, check items or radio items.

First thing to do is to create a menubar.

menubar = wx.MenuBar()

Then we create our menus.

file = wx.Menu()
edit = wx.Menu()
help = wx.Menu()

Then we add some items into the menu. This can be done in two ways.

file.Append(101, '&Open', 'Open a new document')
file.Append(102, '&Save', 'Save the document')

We can separate logical sections in menus with a horizontal line.

file.AppendSeparator()

If you want to have some icons in your menus, you need to create MenuItem objects manually.

quit = wx.MenuItem(file, 105, '&Quit\tCtrl+Q', 'Quit the Application')
quit.SetBitmap(wx.Image('stock_exit-16.png',wx.BITMAP_TYPE_PNG).ConvertToBitmap())
file.AppendItem(quit)

wxPython toolkit can only put bitmaps into menus. Therefore we need to convert our PNG files into bitmaps.

Menus are then added into the menubar.

menubar.Append(file, '&File')
menubar.Append(edit, '&Edit')
menubar.Append(help, '&Help')

Finally we set up our menubar in our application class.

self.SetMenuBar(menubar)

Let's sum it up in a small script.

   1 #!/usr/bin/python
   2 
   3 # menu1.py
   4 
   5 import wx
   6 
   7 class MyMenu(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(200, 150))
  10 
  11         menubar = wx.MenuBar()
  12         file = wx.Menu()
  13         edit = wx.Menu()
  14         help = wx.Menu()
  15         file.Append(101, '&Open', 'Open a new document')
  16         file.Append(102, '&Save', 'Save the document')
  17         file.AppendSeparator()
  18         quit = wx.MenuItem(file, 105, '&Quit\tCtrl+Q', 'Quit the Application')
  19         quit.SetBitmap(wx.Image('stock_exit-16.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap())
  20         file.AppendItem(quit)
  21 
  22         menubar.Append(file, '&File')
  23         menubar.Append(edit, '&Edit')
  24         menubar.Append(help, '&Help')
  25         self.SetMenuBar(menubar)
  26         self.CreateStatusBar()
  27 
  28 class MyApp(wx.App):
  29     def OnInit(self):
  30         frame = MyMenu(None, -1, 'menu1.py')
  31         frame.Show(True)
  32         return True
  33 
  34 app = MyApp(0)
  35 app.MainLoop()

So far we have seen the default, normal menu items. Here we see, how we can explicitly define check items, normal items or radio items.

edit.Append(201, 'check item1', '', wx.ITEM_CHECK)
edit.Append(202, 'check item2', '', kind=wx.ITEM_CHECK)

or

quit = wxMenuItem(file, 105, '&Quit\tCtrl+Q', 'Quit the Application', wx.ITEM_NORMAL)

The parameter is called kind.

Possible wx.ItemKind-s

If you want to create a submenu, you create a menu first.

submenu = wx.Menu()

Then append some menu items into this submenu.

submenu.Append(301, 'radio item1', kind=wx.ITEM_RADIO)
submenu.Append(302, 'radio item2', kind=wx.ITEM_RADIO)
submenu.Append(302, 'radio item3', kind=wx.ITEM_RADIO)

You finish with adding a submenu into a menu object.

edit.AppendMenu(203, 'submenu', submenu)

We now discuss how to respond to user actions. We will touch on it only briefly and explain it later in more detail.

So when a user of our application selects a menu item, an event is generated. We must provide an event handler, that will react to this event accordingly. Handling events in wxPython is the most elegant and simple that I have seen so far. When we look into the reference book, we find wx.EVT_MENU handler under Event handling section.

Suppose we want to add an event handler to our quit menu item.

wx.EVT_MENU(self, 105, self.OnQuit )

We need to provide three pieces of information. The object, where we bind our event handler. In our case self, the application's main object. The id of the corresponding menu item. And the method name, which will do our job.

The method which will react to user actions has two parameters. The first one is the object where this method is defined. The second one is the generated event. This time, we do without it. We simply close our application.

def OnQuit(self, event):
    self.Close()

The following script demonstrates various menu items, submenu and one simple event handler. I hate when my application window pops up somewhere in the corner by the will of the allmighty window manager. So I added

self.Centre()

so that the window pops up in the center of the screen.

menu1.png

Figure: menu1.py

   1 #!/usr/bin/python
   2 
   3 # menu2.py
   4 
   5 import wx
   6 
   7 class MyMenu(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(380, 250))
  10 
  11         menubar = wx.MenuBar()
  12         file = wx.Menu()
  13         edit = wx.Menu()
  14         help = wx.Menu()
  15         file.Append(101, '&Open', 'Open a new document')
  16         file.Append(102, '&Save', 'Save the document')
  17         file.AppendSeparator()
  18         quit = wx.MenuItem(file, 105, '&Quit\tCtrl+Q', 'Quit the Application')
  19         quit.SetBitmap(wx.Image('stock_exit-16.png',wx.BITMAP_TYPE_PNG).ConvertToBitmap())
  20         file.AppendItem(quit)
  21         edit.Append(201, 'check item1', '', wx.ITEM_CHECK)
  22         edit.Append(202, 'check item2', kind=wx.ITEM_CHECK)
  23         submenu = wx.Menu()
  24         submenu.Append(301, 'radio item1', kind=wx.ITEM_RADIO)
  25         submenu.Append(302, 'radio item2', kind=wx.ITEM_RADIO)
  26         submenu.Append(303, 'radio item3', kind=wx.ITEM_RADIO)
  27         edit.AppendMenu(203, 'submenu', submenu)
  28         menubar.Append(file, '&File')
  29         menubar.Append(edit, '&Edit')
  30         menubar.Append(help, '&Help')
  31         self.SetMenuBar(menubar)
  32         self.Centre()
  33         self.Bind(wx.EVT_MENU, self.OnQuit, id=105)
  34 
  35     def OnQuit(self, event):
  36         self.Close()
  37 
  38 class MyApp(wx.App):
  39     def OnInit(self):
  40         frame = MyMenu(None, -1, 'menu2.py')
  41         frame.Show(True)
  42         return True
  43 
  44 app = MyApp(0)
  45 app.MainLoop()

menu2.png

Figure: menu2.py

wx.ToolBar

Toolbar is a widget that groups the most common used commands or actions of your application. Typically save, open, cut, copy, paste, undo, redo etc. It's purpose is to save time. You need one click to do an action from the toolbar and two clicks from the menu.

   1 #!/usr/bin/python
   2 
   3 # toolbar.py
   4 
   5 import wx
   6 
   7 class MyToolBar(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(350, 250))
  10 
  11         vbox = wx.BoxSizer(wx.VERTICAL)
  12         toolbar = wx.ToolBar(self, -1, style=wx.TB_HORIZONTAL | wx.NO_BORDER)
  13         toolbar.AddSimpleTool(1, wx.Image('stock_new.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap(), 'New', '')
  14         toolbar.AddSimpleTool(2, wx.Image('stock_open.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap(), 'Open', '')
  15         toolbar.AddSimpleTool(3, wx.Image('stock_save.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap(), 'Save', '')
  16         toolbar.AddSeparator()
  17         toolbar.AddSimpleTool(4, wx.Image('stock_exit.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap(), 'Exit', '')
  18         toolbar.Realize()
  19         vbox.Add(toolbar, 0, border=5)
  20         self.SetSizer(vbox)
  21         self.statusbar = self.CreateStatusBar()
  22         self.Centre()
  23 
  24         self.Bind(wx.EVT_TOOL, self.OnNew, id=1)
  25         self.Bind(wx.EVT_TOOL, self.OnOpen, id=2)
  26         self.Bind(wx.EVT_TOOL, self.OnSave, id=3)
  27         self.Bind(wx.EVT_TOOL, self.OnExit, id=4)
  28 
  29     def OnNew(self, event):
  30         self.statusbar.SetStatusText('New Command')
  31 
  32     def OnOpen(self, event):
  33         self.statusbar.SetStatusText('Open Command')
  34 
  35     def OnSave(self, event):
  36         self.statusbar.SetStatusText('Save Command')
  37 
  38     def OnExit(self, event):
  39         self.Close()
  40 
  41 class MyApp(wx.App):
  42     def OnInit(self):
  43         frame = MyToolBar(None, -1, 'toolbar.py')
  44         frame.Show(True)
  45         return True
  46 
  47 app = MyApp(0)
  48 app.MainLoop()

wx.BoxSizer will be explained later in layout section. Toolbar widget is created in three steps.

Firstly, we create a toolbar object.

toolbar = wx.ToolBar(self, -1, style=wx.TB_HORIZONTAL | wx.NO_BORDER)

Then we add some tools to the toolbar with the AddSimpleTool() method. You don't find this method in the reference book. It is a wxPython 'extension'. This is a curse and also a blessing. It makes python programming easier. But on the other hand, these extensions are undocumented. You have to look at the wrapper code, demo example or ask on the mailing list.

toolbar.AddSimpleTool(1, wx.Image('stock_new.png',  wx.BITMAP_TYPE_PNG).ConvertToBitmap(), 'New', '')

In the end, we call the Realize() method. This method shows or renders the toolbar widget.

toolbar.Realize()

The toolbar widget has several event handlers. When you click on a toolbar icon a wx.EVT_COMMAND_TOOL_CLICKED event is generated. We bind this event to a specified method with the wx.EVT_TOOL handler.

In order to show some meaningful output to our events, we have set up a statusbar.

self.statusbar = self.CreateStatusBar()

This is yet another wxPython extension. So when we click on a toolbar button, a message is displayed on the statusbar. This is done with the SetStatusText() method.

toolbar.png

Figure: toolbar.py

Layout Management

There are basically two methods for layout of our widgets. The first method is manual. We place widgets by specifying the position in the constructor of the widget.

   1 #!/usr/bin/python
   2 
   3 # layout.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(250, 50))
  10 
  11         panel = wx.Panel(self, -1)
  12         wx.Button(panel, -1, "Button1", (0,0))
  13         wx.Button(panel, -1, "Button2", (80,0))
  14         wx.Button(panel, -1, "Button3", (160,0))
  15 
  16 class MyApp(wx.App):
  17     def OnInit(self):
  18         frame = MyFrame(None, -1, 'layout.py')
  19         frame.Show(True)
  20         frame.Centre()
  21         return True
  22 
  23 app = MyApp(0)
  24 app.MainLoop()

When the window is resized, the size and the position of buttons do not change. This is one of the main features of the manual positioning of the widgets.

layout.png

Figure: layout.py

The second method is to use layout managers. This method is prevalent in real programs. Basically you use sizers. We will discuss

wx.BoxSizer

Let's make a program in which three buttons will occupy one row placed at the top of the window. These buttons will resize when the window is resized.

   1 #!/usr/bin/python
   2 
   3 # wxboxsizer.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, (-1, -1), wx.Size(250, 50))
  10         panel = wx.Panel(self, -1)
  11         box = wx.BoxSizer(wx.HORIZONTAL)
  12         box.Add(wx.Button(panel, -1, 'Button1'), 1 )
  13         box.Add(wx.Button(panel, -1, 'Button2'), 1 )
  14         box.Add(wx.Button(panel, -1, 'Button3'), 1 )
  15         panel.SetSizer(box)
  16         self.Centre()
  17 
  18 class MyApp(wx.App):
  19      def OnInit(self):
  20          frame = MyFrame(None, -1, 'wxboxsizer.py')
  21          frame.Show(True)
  22          return True
  23 
  24 app = MyApp(0)
  25 app.MainLoop()

We can place widgets vertically or horizontally.

box = wx.BoxSizer(integer orient)

where orientation can be wx.VERTICAL or wx.HORIZONTAL. Adding widgets into the wx.BoxSizer is done via the Add() method. In order to understand it, we need to look at its parameters.

Add(wx.Window window, integer proportion=0, integer flag = 0, integer border = 0)

wxboxsizer.png

Figure: wxboxsizer.py

The proportion parameter defines the share or ratio of available sizer space that the widget will occupy in the direction of the defined orientation. Let's assume we have three buttons with the proportions 0, 1, and 2. They are added into a horizontal wx.BoxSizer. The button with proportion 0 will not change at all when the sizer's width (horizontal size) changes (i.e. the button will always be the same width). The rest of the width of the sizer is split into 3 (2+1) shares. The button with proportion 2 will always occupy 2 of those 3 shares (its width will be 2/3 of the available width), and the button with proportion 1 will always occupy 1 of those shares.

With the flag parameter, you can further configure the behaviour of the widgets within a wx.BoxSizer. We can control the border (though "padding" would be a more accurate name than "border") between the widgets. We add some space between widgets in pixels. In order to apply border, we need to define which sides will use the border. We can choose between these flags:

We can combine them with the | operator. e.g wx.LEFT | wx.BOTTOM. If we use wx.EXPAND flag, our widget will use all the space that is available in the direction perpendicular to the sizer's orient direction. Lastly, we can also define the alignment of our widgets. We do it with the following flags :

An example:

   1 #!/usr/bin/python
   2 
   3 # layout3.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, (-1, -1), wx.Size(450, 300))
  10 
  11         panel = wx.Panel(self, -1)
  12         box = wx.BoxSizer(wx.HORIZONTAL)
  13         box.Add(wx.Button(panel, -1, 'Button1'), 1, wx.ALL, 5)
  14         box.Add(wx.Button(panel, -1, 'Button2'), 0, wx.EXPAND)
  15         box.Add(wx.Button(panel, -1, 'Button3'), 0, wx.ALIGN_CENTER)
  16         panel.SetSizer(box)
  17         self.Centre()
  18 
  19 class MyApp(wx.App):
  20     def OnInit(self):
  21         frame = MyFrame(None, -1, 'layout3.py')
  22         frame.Show(True)
  23         return True
  24 
  25 app = MyApp(0)
  26 app.MainLoop()

In our example we again have three buttons. The first one has some border around all its sides. It is the only one that changes in the horizontal dimension when the main window is resized. The second one occupies all space alloted to it in the vertical direction. The third one is aligned in the centre.

We can combine various wx.BoxSizer-s. For example, we can put several horizontal wx.BoxSizer-s into a vertical wx.BoxSizer and vice versa. This way we can make complex layouts.

   1 #!/usr/bin/python
   2 
   3 # borders.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9 
  10         wx.Frame.__init__(self, parent, id, title)
  11         hbox = wx.BoxSizer(wx.HORIZONTAL)
  12         pnl1 = wx.Panel(self, -1, style=wx.SIMPLE_BORDER)
  13         pnl2 = wx.Panel(self, -1, style=wx.RAISED_BORDER)
  14         pnl3 = wx.Panel(self, -1, style=wx.SUNKEN_BORDER)
  15         pnl4 = wx.Panel(self, -1, style=wx.NO_BORDER)
  16 
  17         hbox.Add(pnl1, 1, wx.EXPAND | wx.ALL, 3)
  18         hbox.Add(pnl2, 1, wx.EXPAND | wx.ALL, 3)
  19         hbox.Add(pnl3, 1, wx.EXPAND | wx.ALL, 3)
  20         hbox.Add(pnl4, 1, wx.EXPAND | wx.ALL, 3)
  21 
  22         self.SetSize((400, 120))
  23         self.SetSizer(hbox)
  24         self.Centre()
  25 
  26 class MyApp(wx.App):
  27     def OnInit(self):
  28         frame = MyFrame(None, -1, 'borders.py')
  29         frame.Show(True)
  30         return True
  31 
  32 app = MyApp(0)
  33 app.MainLoop()

We show four various border styles available in wxPython. Border is a simple window decoration.

Available Borders:

borders.png

wx.GridSizer

wx.GridSizer lays out its children in a two-dimensional table. The width of each field is the width of the widest child. The height of each field is the height of the tallest child.

wx.GridSizer(integer rows, integer cols, integer vgap, integer hgap)

In the constructor we provide the number of rows and the number of columns of our table and the horizontal and vertical gap between the children widgets. We insert our widgets into the table with the AddMany() method. Children are inserted from left to right, top to bottom.

   1 #!/usr/bin/python
   2 
   3 # calculator.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9 
  10         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(300, 250))
  11         self.formula = False
  12         menubar = wx.MenuBar()
  13         file = wx.Menu()
  14         file.Append(22, '&Quit', 'Exit Calculator')
  15         menubar.Append(file, '&File')
  16         self.SetMenuBar(menubar)
  17         wx.EVT_MENU(self, 22, self.OnClose)
  18         sizer = wx.BoxSizer(wx.VERTICAL)
  19         self.display = wx.TextCtrl(self, -1, '',  style=wx.TE_RIGHT)
  20         sizer.Add(self.display, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 4)
  21 
  22         gs = wx.GridSizer(4, 4, 3, 3)
  23         gs.AddMany([(wx.Button(self, 20, 'Cls'), 0, wx.EXPAND),
  24                         (wx.Button(self, 21, 'Bck'), 0, wx.EXPAND),
  25                         (wx.StaticText(self, -1, ''), 0, wx.EXPAND),
  26                         (wx.Button(self, 22, 'Close'), 0, wx.EXPAND),
  27                         (wx.Button(self, 1, '7'), 0, wx.EXPAND),
  28                         (wx.Button(self, 2, '8'), 0, wx.EXPAND),
  29                         (wx.Button(self, 3, '9'), 0, wx.EXPAND),
  30                         (wx.Button(self, 4, '/'), 0, wx.EXPAND),
  31                         (wx.Button(self, 5, '4'), 0, wx.EXPAND),
  32                         (wx.Button(self, 6, '5'), 0, wx.EXPAND),
  33                         (wx.Button(self, 7, '6'), 0, wx.EXPAND),
  34                         (wx.Button(self, 8, '*'), 0, wx.EXPAND),
  35                         (wx.Button(self, 9, '1'), 0, wx.EXPAND),
  36                         (wx.Button(self, 10, '2'), 0, wx.EXPAND),
  37                         (wx.Button(self, 11, '3'), 0, wx.EXPAND),
  38                         (wx.Button(self, 12, '-'), 0, wx.EXPAND),
  39                         (wx.Button(self, 13, '0'), 0, wx.EXPAND),
  40                         (wx.Button(self, 14, '.'), 0, wx.EXPAND),
  41                         (wx.Button(self, 15, '='), 0, wx.EXPAND),
  42                         (wx.Button(self, 16, '+'), 0, wx.EXPAND) ])
  43 
  44         sizer.Add(gs, 1, wx.EXPAND)
  45 
  46         self.SetSizer(sizer)
  47         self.Centre()
  48 
  49         self.Bind(wx.EVT_BUTTON, self.OnClear, id=20)
  50         self.Bind(wx.EVT_BUTTON, self.OnBackspace, id=21)
  51         self.Bind(wx.EVT_BUTTON, self.OnClose, id=22)
  52         self.Bind(wx.EVT_BUTTON, self.OnSeven, id=1)
  53         self.Bind(wx.EVT_BUTTON, self.OnEight, id=2)
  54         self.Bind(wx.EVT_BUTTON, self.OnNine, id=3)
  55         self.Bind(wx.EVT_BUTTON, self.OnDivide, id=4)
  56         self.Bind(wx.EVT_BUTTON, self.OnFour, id=5)
  57         self.Bind(wx.EVT_BUTTON, self.OnFive, id=6)
  58         self.Bind(wx.EVT_BUTTON, self.OnSix, id=7)
  59         self.Bind(wx.EVT_BUTTON, self.OnMultiply, id=8)
  60         self.Bind(wx.EVT_BUTTON, self.OnOne, id=9)
  61         self.Bind(wx.EVT_BUTTON, self.OnTwo, id=10)
  62         self.Bind(wx.EVT_BUTTON, self.OnThree, id=11)
  63         self.Bind(wx.EVT_BUTTON, self.OnMinus, id=12)
  64         self.Bind(wx.EVT_BUTTON, self.OnZero, id=13)
  65         self.Bind(wx.EVT_BUTTON, self.OnDot, id=14)
  66         self.Bind(wx.EVT_BUTTON, self.OnEqual, id=15)
  67         self.Bind(wx.EVT_BUTTON, self.OnPlus, id=16)
  68 
  69     def OnClear(self, event):
  70         self.display.Clear()
  71 
  72     def OnBackspace(self, event):
  73         formula = self.display.GetValue()
  74         self.display.Clear()
  75         self.display.SetValue(formula[:-1])
  76 
  77     def OnClose(self, event):
  78         self.Close()
  79 
  80     def OnDivide(self, event):
  81         if self.formula:
  82             return
  83         self.display.AppendText('/')
  84 
  85     def OnMultiply(self, event):
  86         if self.formula:
  87             return
  88         self.display.AppendText('*')
  89 
  90     def OnMinus(self, event):
  91         if self.formula:
  92             return
  93         self.display.AppendText('-')
  94 
  95     def OnPlus(self, event):
  96         if self.formula:
  97             return
  98         self.display.AppendText('+')
  99 
 100     def OnDot(self, event):
 101         if self.formula:
 102             return
 103         self.display.AppendText('.')
 104 
 105     def OnEqual(self, event):
 106         if self.formula:
 107             return
 108         formula = self.display.GetValue()
 109         self.formula = False
 110         try:
 111             self.display.Clear()
 112             output = eval(formula)
 113             self.display.AppendText(str(output))
 114         except StandardError:
 115             self.display.AppendText("Error")
 116 
 117     def OnZero(self, event):
 118         if self.formula:
 119             self.display.Clear()
 120             self.formula = False
 121         self.display.AppendText('0')
 122 
 123     def OnOne(self, event):
 124         if self.formula:
 125             self.display.Clear()
 126             self.formula = False
 127         self.display.AppendText('1')
 128 
 129     def OnTwo(self, event):
 130         if self.formula:
 131             self.display.Clear()
 132             self.formula = False
 133         self.display.AppendText('2')
 134 
 135     def OnThree(self, event):
 136         if self.formula:
 137             self.display.Clear()
 138             self.formula = False
 139         self.display.AppendText('3')
 140 
 141     def OnFour(self, event):
 142         if self.formula:
 143             self.display.Clear()
 144             self.formula = False
 145         self.display.AppendText('4')
 146 
 147     def OnFive(self, event):
 148         if self.formula:
 149             self.display.Clear()
 150             self.formula = False
 151         self.display.AppendText('5')
 152 
 153     def OnSix(self, event):
 154         if self.formula:
 155             self.display.Clear()
 156             self.formula = False
 157         self.display.AppendText('6')
 158 
 159     def OnSeven(self, event):
 160         if self.formula:
 161             self.display.Clear()
 162             self.formula = False
 163         self.display.AppendText('7')
 164 
 165     def OnEight(self, event):
 166         if self.formula:
 167             self.display.Clear()
 168             self.formula = False
 169         self.display.AppendText('8')
 170 
 171     def OnNine(self, event):
 172         if self.formula:
 173             self.display.Clear()
 174             self.formula = False
 175         self.display.AppendText('9')
 176 
 177 class MyApp(wx.App):
 178     def OnInit(self):
 179         frame = MyFrame(None, -1, 'calculator.py')
 180         frame.Show(True)
 181         self.SetTopWindow(frame)
 182         return True
 183 
 184 app = MyApp(0)
 185 app.MainLoop()

The formula we input is processed by the eval built-in python function.

output = eval(formula)

If we make an error in our formula, an error message is displayed. Notice how we managed to put a space between the Bck and Close buttons. We simply put an empty wx.StaticText there. Such tricks are quite common.

calculator.png

Figure: calculator.py

wx.GridBagSizer

The most complicated sizer in wxPython. It enables explicit positioning of the items. Items can also optionally span more than one row and/or column. wx.GridBagSizer has a simple constructor.

wx.GridBagSizer(integer vgap, integer hgap)

The vertical and the horizontal gap defines the space in pixels used between children. You add items to grid with the Add() method.

Add(self, item, tuple pos, tuple span=wx.DefaultSpan, integer flag=0, integer border=0, userData=None)

Item is a widget that you insert into the grid. pos specifies the position in the virtual grid. The topleft cell has pos of (0, 0). span is an optional spanning of the widget. e.g. span of (3, 2) spans a widget across 3 rows and 2 columns. flag and border were discussed earlier by wx.BoxSizer.

The items in the grid can change their size or keep the default size, when the window is resized. If you want your items to grow and shrink, you can use these two methods.

AddGrowableRow(integer row)
AddGrowableCol(integer col)

   1 #!/usr/bin/python
   2 
   3 # wxgridbagsizer.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition)
  10 
  11         sizer = wx.GridBagSizer(9, 9)
  12         sizer.Add(wx.Button(self,-1, "Button"), (0, 0), wx.DefaultSpan,  wx.ALL, 5)
  13         sizer.Add(wx.Button(self,-1, "Button"), (1, 1), (1,7), wx.EXPAND)
  14         sizer.Add(wx.Button(self,-1, "Button"), (6, 6), (3,3), wx.EXPAND)
  15         sizer.Add(wx.Button(self,-1, "Button"), (3, 0), (1,1), wx.ALIGN_CENTER)
  16         sizer.Add(wx.Button(self,-1, "Button"), (4, 0), (1,1), wx.ALIGN_LEFT)
  17         sizer.Add(wx.Button(self,-1, "Button"), (5, 0), (1,1), wx.ALIGN_RIGHT)
  18         sizer.AddGrowableRow(6)
  19         sizer.AddGrowableCol(6)
  20 
  21         self.SetSizerAndFit(sizer)
  22         self.Centre()
  23 
  24 class MyApp(wx.App):
  25     def OnInit(self):
  26         frame = MyFrame(None, -1, "wxgridbagsizer.py")
  27         frame.Show(True)
  28         self.SetTopWindow(frame)
  29         return True
  30 
  31 app = MyApp(0)
  32 app.MainLoop()

If you want your item to span more than one cell, you must provide wx.EXPAND flag.

self.SetSizerAndFit(sizer)

This method is same as SetSizer() except that it also sends size hints to the window. All buttons are displayed on the window.

Basic Objects

wxPython is a collection of various objects. We can divide them into two groups.

Examples of visual objects are: widgets, fonts, colours or cursors. Non-visual objects: sizers, timers or events.

Cursors

A cursor is a simple graphical object. It is used to indicate position on the monitor or any other display device. It usually dynamically changes itself. Typically, when you hover a mouse pointer over a hypertext, the cursor changes to a hand.

In the next code example, we create a grid of nine wx.Panels. Each panel shows a different cursor.

   1 #!/usr/bin/python
   2 
   3 # cursors.py
   4 
   5 import wx
   6 
   7 class Cursors(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title)
  10 
  11         vbox = wx.BoxSizer(wx.VERTICAL)
  12         sizer = wx.GridSizer(3, 3, 2, 2)
  13 
  14         cursors = [ wx.CURSOR_ARROW, wx.CURSOR_HAND, wx.CURSOR_WATCH, wx.CURSOR_SPRAYCAN, wx.CURSOR_PENCIL,
  15                     wx.CURSOR_CROSS, wx.CURSOR_QUESTION_ARROW, wx.CURSOR_POINT_LEFT, wx.CURSOR_SIZING]
  16 
  17         for i in cursors:
  18             panel = wx.Panel(self, -1, style=wx.SUNKEN_BORDER)
  19             panel.SetCursor(wx.StockCursor(i))
  20             sizer.Add(panel, flag=wx.EXPAND)
  21 
  22         vbox.Add(sizer, 1, wx.EXPAND | wx.TOP, 5)
  23         self.SetSizer(vbox)
  24 
  25         self.Centre()
  26         self.Show()
  27 
  28 
  29 app = wx.App(0)
  30 Cursors(None, -1, 'Cursors.py')
  31 app.MainLoop()

Various predefined cursors: Listed below.

Fonts

We create different kinds of fonts with the wx.Font object. It has the following constructor:

wx.Font(integer pointSize, wx.FontFamily family, integer style, integer weight,
        bool underline = false, string faceName = '',
        wx.FontEncoding encoding = wx.FONTENCODING_DEFAULT)

The specified parameters can have the following options:

family:

style:

weight:

fonts.py script shows three different fonts.

   1 #!/usr/bin/python
   2 
   3 # fonts.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(325, 320))
  10 
  11         panel = wx.Panel(self, -1)
  12 
  13         text1 = "Now listen to me mama\nMama mama\nYou're taking away my last chance\nDon't take it away"
  14 
  15         text2 = '''You won't cry for my absence, I know -
  16 You forgot me long ago.
  17 Am I that unimportant...?
  18 Am I so insignificant...?
  19 Isn't something missing?
  20 Isn't someone missing me?'''
  21 
  22         text3 = '''But if I had one wish fulfilled tonight
  23 I'd ask for the sun to never rise
  24 If God passed a mic to me to speak
  25 I'd say stay in bed, world
  26 Sleep in peace'''
  27 
  28         font1 = wx.Font(10, wx.NORMAL, wx.ITALIC, wx.NORMAL)
  29         font2 = wx.Font(10, wx.ROMAN, wx.NORMAL, wx.NORMAL)
  30         font3 = wx.Font(10, wx.MODERN, wx.NORMAL, wx.BOLD)
  31         lyrics1 = wx.StaticText(panel, -1, text1,(30,15), style=wx.ALIGN_CENTRE)
  32         lyrics1.SetFont(font1)
  33         lyrics2 = wx.StaticText(panel, -1, text2,(30,100), style=wx.ALIGN_CENTRE)
  34         lyrics2.SetFont(font2)
  35         lyrics3 = wx.StaticText(panel, -1, text3,(5,220), style=wx.ALIGN_CENTRE)
  36         lyrics3.SetFont(font3)
  37         self.Center()
  38 
  39 class MyApp(wx.App):
  40     def OnInit(self):
  41         frame = MyFrame(None, -1, 'fonts.py')
  42         frame.Show(True)
  43         self.SetTopWindow(frame)
  44         return True
  45 
  46 app = MyApp(0)
  47 app.MainLoop()

fonts.png

Figure: fonts.py

Colours

A colour is an object representing a combination of Red, Green, and Blue (RGB) intensity values. Valid RGB 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.

SetBackgroundColour(wx.Colour(0,0,255))
SetBackgroundColour('BLUE')
SetBackgroundColour('#0000FF')

Predefined colour names in wxPython:

Standard colour database:

Listed below.

colours.py script shows eight different colours. wxBlack is simple. sea green is poetic and #0000FF technical. It is up to the developer, what to choose.

   1 #!/usr/bin/python
   2 
   3 # colours.py
   4 
   5 import wx
   6 
   7 
   8 class Colours(wx.Dialog):
   9     def __init__(self, parent, id, title):
  10 
  11         wx.Dialog.__init__(self, parent, id, title, size=(300, 300))
  12         vbox = wx.BoxSizer(wx.VERTICAL)
  13         self.pnl1 = wx.Panel(self, -1)
  14         self.pnl2 = wx.Panel(self, -1)
  15         self.pnl3 = wx.Panel(self, -1)
  16         self.pnl4 = wx.Panel(self, -1)
  17         self.pnl5 = wx.Panel(self, -1)
  18         self.pnl6 = wx.Panel(self, -1)
  19         self.pnl7 = wx.Panel(self, -1)
  20         self.pnl8 = wx.Panel(self, -1)
  21 
  22         gs = wx.GridSizer(4,2,3,3)
  23         gs.AddMany([ (self.pnl1, 0 ,wx.EXPAND),
  24             (self.pnl2, 0, wx.EXPAND),
  25             (self.pnl3, 0, wx.EXPAND),
  26             (self.pnl4, 0, wx.EXPAND),
  27             (self.pnl5, 0, wx.EXPAND),
  28             (self.pnl6, 0, wx.EXPAND),
  29             (self.pnl7, 0, wx.EXPAND),
  30             (self.pnl8, 0, wx.EXPAND) ])
  31 
  32         vbox.Add(gs, 1, wx.EXPAND | wx.TOP, 5)
  33         self.SetSizer(vbox)
  34         self.SetColors()
  35         self.Centre()
  36         self.ShowModal()
  37         self.Destroy()
  38 
  39     def SetColors(self):
  40         self.pnl1.SetBackgroundColour(wx.BLACK)
  41         self.pnl2.SetBackgroundColour(wx.Colour(139,105,20))
  42         self.pnl3.SetBackgroundColour(wx.RED)
  43         self.pnl4.SetBackgroundColour('#0000FF')
  44         self.pnl5.SetBackgroundColour('sea green')
  45         self.pnl6.SetBackgroundColour('midnight blue')
  46         self.pnl7.SetBackgroundColour(wx.LIGHT_GREY)
  47         self.pnl8.SetBackgroundColour('plum')
  48 
  49 app = wx.App(0)
  50 Colours(None, -1, 'colours.py')
  51 app.MainLoop()

colours.png

Figure: colours.py

The full database has currently about 630 different colour names. The list can be found in the colourdb.py file. It is also shown in the wxPython demo. In randomcolours.py script we have a single window. We change the background colour of the window to the randomly chosen colour.

   1 #!/usr/bin/python
   2 
   3 # randomcolours.py
   4 
   5 import wx
   6 
   7 from random import randrange
   8 from wx.lib.colourdb import *
   9 
  10 class MyFrame(wx.Frame):
  11     def __init__(self, parent, id, title):
  12         wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(400, 350))
  13 
  14         self.panel = wx.Panel(self, -1)
  15         self.colors = getColourList()
  16         self.timer = wx.Timer(self)
  17         self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
  18         self.timer.Start(1500)
  19         self.col_num = len(self.colors)
  20         self.Centre()
  21 
  22     def OnTimer(self, event):
  23         self.panel.SetBackgroundColour(wx.RED)
  24         position = randrange(0, self.col_num-1, 1)
  25         self.panel.SetBackgroundColour(self.colors[position])
  26         self.panel.Refresh()
  27 
  28 class MyApp(wx.App):
  29     def OnInit(self):
  30         updateColourDB()
  31         frame = MyFrame(None, -1, 'randomcolours.py')
  32         frame.Show(True)
  33         self.SetTopWindow(frame)
  34         return True
  35 
  36 app = MyApp(0)
  37 app.MainLoop()

Bitmaps

There are two kinds of graphics. Vector and bitmap. With vector graphics, images are created with mathematical formulas that define all the shapes of the image. Geometric objects like curves and polygons are used. A bitmap or a bit map 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.

There are various kinds of bitmaps:

   1 #!/usr/bin/python
   2 
   3 # bitmap.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, size = (270, 270))
  10 
  11         self.bitmap = wx.Bitmap('memento.jpg')
  12         wx.EVT_PAINT(self, self.OnPaint)
  13 
  14         self.Centre()
  15 
  16     def OnPaint(self, event):
  17         dc = wx.PaintDC(self)
  18         dc.DrawBitmap(self.bitmap, 60, 20)
  19 
  20 
  21 class MyApp(wx.App):
  22     def OnInit(self):
  23         frame = MyFrame(None, -1, 'Memento')
  24         frame.Show(True)
  25         self.SetTopWindow(frame)
  26         return True
  27 
  28 app = MyApp(0)
  29 app.MainLoop()

memento.png

Figure: bitmap.py

Events

Events are integral part of every GUI application. All GUI applications are event-driven. An application reacts to different event types which are generated during it's life. Events are generated mainly by the user of an application. But they can be generated by other means as well. e.g. internet connection, window manager, timer. So when we call MainLoop() method, our application waits for events to be generated. The MainLoop() method ends when we exit the application.

Working with events is straightforward in wxPython. There are three steps:

In wxPython we say to bind a method to an event. Sometimes a word hook is used.

You bind an event by calling the Bind() method. The method has the following parameters:

Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

Note that method Bind() is defined in class EvtHandler. It is the class, from which wx.Window inherits. wx.Window is a base class for most widgets in wxPython. There is also a reverse process. If we want to unbind a method from an event, we call the Unbind() method. It has the same paremeters as the above one.

Possible events

wx.Event

the event base class

wx.ActivateEvent

a window or application activation event

wx.CloseEvent

a close window or end session event

wx.EraseEvent

an erase background event

wx.FocusEvent

a window focus event

wx.KeyEvent

a keypress event

wx.IdleEvent

an idle event

wx.InitDialogEvent

a dialog initialisation event

wx.JoystickEvent

a joystick event

wx.MenuEvent

a menu event

wx.MouseEvent

a mouse event

wx.MoveEvent

a move event

wx.PaintEvent

a paint event

wx.QueryLayoutInfoEvent

used to query layout information

wx.SetCursorEvent

used for special cursor processing based on current mouse position

wx.SizeEvent

a size event

wx.ScrollWinEvent

a scroll event sent by a built-in Scrollbar

wx.ScrollEvent

a scroll event sent by a stand-alone scrollbar

wx.SysColourChangedEvent

a system colour change event

Examples

The following code is an example of a wx.ScrollWinEvent. This event is generated, when we click on a built in Scrollbar. Built-in Scrollbar is activated with the SetScrollbar() method call. For stand-alone Scrollbars, there is another event type, namely wx.ScrollEvent.

   1 #!/usr/bin/python
   2 
   3 # myscrollwinevent.py
   4 
   5 import wx
   6 
   7 class MyScrollWinEvent(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title)
  10         panel = wx.Panel(self, -1)
  11         self.st = wx.StaticText(panel, -1, '0', (30,0))
  12         panel.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
  13         panel.SetScrollbar(wx.VERTICAL, 0, 6, 50);
  14         self.Centre()
  15 
  16     def OnScroll(self, evt):
  17         y = evt.GetPosition()
  18         self.st.SetLabel(str(y))
  19 
  20 class MyApp(wx.App):
  21     def OnInit(self):
  22         msw = MyScrollWinEvent(None, -1, 'myscrollwinevent.py')
  23         msw.Show(True)
  24         return True
  25 
  26 app = MyApp(0)
  27 app.MainLoop()

The wx.SizeEvent is generated, when our window is resized. In our example, we show the size of the window in the titlebar.

   1 #!/usr/bin/python
   2 
   3 # sizeevent.py
   4 
   5 import wx
   6 
   7 class SizeEvent(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title)
  10 
  11         self.Bind(wx.EVT_SIZE, self.OnSize)
  12         self.Centre()
  13 
  14     def OnSize(self, event):
  15         self.SetTitle(str(event.GetSize()))
  16 
  17 class MyApp(wx.App):
  18     def OnInit(self):
  19         se = SizeEvent(None, -1, 'sizeevent.py')
  20         se.Show(True)
  21         return True
  22 
  23 app = MyApp(0)
  24 app.MainLoop()

   1 #!/usr/bin/python
   2 
   3 # moveevent.py
   4 
   5 import wx
   6 
   7 class MoveEvent(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title)
  10 
  11         wx.StaticText(self, -1, 'x:', (10,0))
  12         wx.StaticText(self, -1, 'y:', (10,20))
  13         self.st1 = wx.StaticText(self, -1, '', (30, 0))
  14         self.st2 = wx.StaticText(self, -1, '', (30, 20))
  15         self.Bind(wx.EVT_MOVE, self.OnMove)
  16         self.Centre()
  17 
  18     def OnMove(self, event):
  19         x, y = event.GetPosition()
  20         self.st1.SetLabel(str(x))
  21         self.st2.SetLabel(str(y))
  22 
  23 class MyApp(wx.App):
  24     def OnInit(self):
  25         me = MoveEvent(None, -1, 'moveevent.py')
  26         me.Show(True)
  27         return True
  28 
  29 app = MyApp(0)
  30 app.MainLoop()

A paint event is generated when a window is redrawn. This happens when we resize window, maximize it. A paint event can be generated programatically as well. For example, when we call SetLabel() method to change a wxStaticText widget. Note that when we minimize a window, no paint event is generated.

   1 #!/usr/bin/python
   2 
   3 # paintevent.py
   4 
   5 import wx
   6 
   7 class PaintEvent(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title)
  10 
  11         self.Bind(wx.EVT_PAINT, self.OnPaint)
  12         self.Centre()
  13 
  14     def OnPaint(self, event):
  15         wx.Bell()
  16 
  17 class MyApp(wx.App):
  18     def OnInit(self):
  19         pe = PaintEvent(None, -1, 'paintevent.py')
  20         pe.Show(True)
  21         return True
  22 
  23 app = MyApp(0)
  24 app.MainLoop()

When we press a key on our keyboard, wx.KeyEvent is generated. There are three different key handlers:

A common request is to close application, when Esc key is pressed.

   1 #!/usr/bin/python
   2 
   3 # keyevent.py
   4 
   5 import wx
   6 
   7 
   8 class KeyEvent(wx.Frame):
   9     def __init__(self, parent, id, title):
  10         wx.Frame.__init__(self, parent, id, title)
  11 
  12         panel = wx.Panel(self, -1)
  13         panel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  14         panel.SetFocus()
  15 
  16         self.Centre()
  17         self.Show(True)
  18 
  19 
  20     def OnKeyDown(self, event):
  21         keycode = event.GetKeyCode()
  22         if keycode == wx.WXK_ESCAPE:
  23             ret  = wx.MessageBox('Are you sure to quit?', 'Question', wx.YES_NO | wx.CENTRE |
  24  wx.NO_DEFAULT, self)
  25             if ret == wx.YES:
  26                 self.Close()
  27         event.Skip()
  28 
  29 
  30 app = wx.App()
  31 KeyEvent(None, -1, 'keyevent.py')
  32 app.MainLoop()

We find out, which key was pressed by calling GetKeyCode() method. In our case, keycode is wx.WXK_ESCAPE.

keycode = event.GetKeyCode()

Other keycodes are listed below.

Dialogs

In wxPython you can use predefined dialogs or create your own dialogs. You can also create dialog based applications.

The example shows a skeleton of a dialog based application in wxPython.

   1 #!/usr/bin/python
   2 # simpledialog.py
   3 
   4 import wx
   5 
   6 class MyDialog(wx.Dialog):
   7     def __init__(self, parent, id, title):
   8         wx.Dialog.__init__(self, parent, id, title)
   9 
  10 class MyApp(wx.App):
  11     def OnInit(self):
  12         dia = MyDialog(None, -1, "simpledialog.py")
  13         dia.ShowModal()
  14         dia.Destroy()
  15         return True
  16 
  17 app = MyApp(0)
  18 app.MainLoop()

Notice that you cannot resize the dialog window. The Destroy() method is necessary. It deletes the dialog from the memory. Otherwise, the script would not exit properly.

There are two types of dialogs. Modal and modeless. Modal dialog does not allow a user to work with the rest of the application until it is destroyed. Modal dialogs are created with the ShowModal() method. Dialogs are modeless when called with Show().

Custom dialogs

There are two methods that simplify the creation of dialogs. Both return a specific sizer object.

CreateTextSizer(self, string message)
CreateButtonSizer(self, long flags)

CreateTextSizer() method creates a text sizer. In the following example, we add some buttons into the sizer to demonstrate it.

   1 #!/usr/bin/python
   2 
   3 # customdialog1.py
   4 
   5 import wx
   6 
   7 class MyDialog(wx.Dialog):
   8     def __init__(self, parent, id, title):
   9         wx.Dialog.__init__(self, parent, id, title, size=(350,300))
  10 
  11         sizer =  self.CreateTextSizer('My Buttons')
  12         sizer.Add(wx.Button(self, -1, 'Button'), 0, wx.ALL, 5)
  13         sizer.Add(wx.Button(self, -1, 'Button'), 0, wx.ALL, 5)
  14         sizer.Add(wx.Button(self, -1, 'Button'), 0, wx.ALL, 5)
  15         sizer.Add(wx.Button(self, -1, 'Button'), 0, wx.ALL|wx.ALIGN_CENTER, 5)
  16         sizer.Add(wx.Button(self, -1, 'Button'), 0, wx.ALL|wx.EXPAND, 5)
  17         self.SetSizer(sizer)
  18 
  19 class MyFrame(wx.Frame):
  20     def __init__(self, parent, id, title):
  21         wx.Frame.__init__(self, parent, id, title, size=(550,500))
  22 
  23         panel = wx.Panel(self, -1)
  24         wx.Button(panel, 1, 'Show Custom Dialog', (100,100))
  25         self.Bind (wx.EVT_BUTTON, self.OnShowCustomDialog, id=1)
  26 
  27     def OnShowCustomDialog(self, event):
  28         dia = MyDialog(self, -1, 'buttons')
  29         dia.ShowModal()
  30         dia.Destroy()
  31 
  32 class MyApp(wx.App):
  33     def OnInit(self):
  34         frame = MyFrame(None, -1, 'customdialog1.py')
  35         frame.Show(True)
  36         frame.Centre()
  37         return True
  38 
  39 app = MyApp(0)
  40 app.MainLoop()

CreateButtonSizer() method creates a row of buttons. You can specify button types with different flags. CreateButtonSizer() method can take the following flags:

   1 #!/usr/bin/python
   2 
   3 # customdialog2.py
   4 
   5 import wx
   6 
   7 class MyDialog(wx.Dialog):
   8     def __init__(self, parent, id, title):
   9         wx.Dialog.__init__(self, parent, id, title)
  10 
  11         vbox = wx.BoxSizer(wx.VERTICAL)
  12         stline = wx.StaticText(self, 11, 'Discipline ist Macht.')
  13         vbox.Add(stline, 1, wx.ALIGN_CENTER|wx.TOP, 45)
  14         sizer =  self.CreateButtonSizer(wx.NO|wx.YES|wx.HELP)
  15         vbox.Add(sizer, 0, wx.ALIGN_CENTER)
  16         self.SetSizer(vbox)
  17         self.Bind(wx.EVT_BUTTON, self.OnYes, id=wx.ID_YES)
  18 
  19     def OnYes(self, event):
  20         self.Close()
  21 
  22 class MyFrame(wx.Frame):
  23     def __init__(self, parent, id, title):
  24         wx.Frame.__init__(self, parent, id, title)
  25         panel = wx.Panel(self, -1)
  26         wx.Button(panel, 1, 'Show custom Dialog', (50,50))
  27         self.Bind(wx.EVT_BUTTON, self.OnShowCustomDialog, id=1)
  28 
  29     def OnShowCustomDialog(self, event):
  30         dia = MyDialog(self, -1, '')
  31         val = dia.ShowModal()
  32         dia.Destroy()
  33 
  34 class MyApp(wx.App):
  35     def OnInit(self):
  36         frame = MyFrame(None, -1, 'customdialog2.py')
  37         frame.Show(True)
  38         frame.Centre()
  39         return True
  40 
  41 app = MyApp(0)
  42 app.MainLoop()

Note that wxPython does not take the order of flags into account.

sizer =  self.CreateButtonSizer(wxNO|wxYES|wxHELP)

The buttons will be created according to the standards.

Common Predefined Dialogs

wxPython provides several common dialogs. They save programmers a lot of work. They also help promote standards in applications. We will show these ones:

   1 #!/usr/bin/python
   2 
   3 # commondialogs.py
   4 
   5 import wx
   6 import os, sys
   7 
   8 class MyFrame(wx.Frame):
   9     def __init__(self, parent, id, title):
  10       wx.Frame.__init__(self, parent, id, title)
  11 
  12       self.CreateStatusBar()
  13       menuBar = wx.MenuBar()
  14       menu = wx.Menu()
  15       menu.Append(99,  "&Message Dialog", "Shows a Message Dialog")
  16       menu.Append(100, "&Color Dialog", "Shows a Color Dialog")
  17       menu.Append(101, "&File Dialog", "Shows a File Dialog")
  18       menu.Append(102, "&Page Setup Dialog", "Shows a Page Setup Dialog")
  19       menu.Append(103, "&Font Dialog", "Shows a Font Dialog")
  20       menu.Append(104, "&Directory Dialog", "Shows a Directory Dialog")
  21       menu.Append(105, "&SingleChoice Dialog", "Shows a SingleChoice Dialog")
  22       menu.Append(106, "&TextEntry Dialog", "Shows a TextEntry Dialog")
  23       menuBar.Append(menu, "&Dialogs")
  24       self.SetMenuBar(menuBar)
  25 
  26       self.Bind(wx.EVT_MENU, self.message, id=99)
  27       self.Bind(wx.EVT_MENU, self.choosecolor, id=100)
  28       self.Bind(wx.EVT_MENU, self.openfile, id=101)
  29       self.Bind(wx.EVT_MENU, self.pagesetup, id=102)
  30       self.Bind(wx.EVT_MENU, self.choosefont, id=103)
  31       self.Bind(wx.EVT_MENU, self.opendir, id=104)
  32       self.Bind(wx.EVT_MENU, self.singlechoice, id=105)
  33       self.Bind(wx.EVT_MENU, self.textentry, id=106)
  34 
  35     def message(self, event):
  36         dlg = wx.MessageDialog(self, 'To save one life is as if you have saved the world.', 'Talmud', wx.OK|wx.ICON_INFORMATION)
  37         dlg.ShowModal()
  38         dlg.Destroy()
  39 
  40     def choosecolor(self, event):
  41         dlg = wx.ColourDialog(self)
  42         dlg.GetColourData().SetChooseFull(True)
  43         if dlg.ShowModal() == wx.ID_OK:
  44             data = dlg.GetColourData()
  45             self.SetStatusText('You selected: %s\n' % str(data.GetColour().Get()))
  46         dlg.Destroy()
  47 
  48     def openfile(self, event):
  49        dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.*", wx.OPEN)
  50        if dlg.ShowModal() == wx.ID_OK:
  51                 path = dlg.GetPath()
  52                 mypath = os.path.basename(path)
  53                 self.SetStatusText("You selected: %s" % mypath)
  54        dlg.Destroy()
  55 
  56     def pagesetup(self, event):
  57         dlg = wx.PageSetupDialog(self)
  58         if dlg.ShowModal() == wx.ID_OK:
  59             data = dlg.GetPageSetupData()
  60             tl = data.GetMarginTopLeft()
  61             br = data.GetMarginBottomRight()
  62             self.SetStatusText('Margins are: %s %s' % (str(tl), str(br)))
  63         dlg.Destroy()
  64 
  65     def choosefont(self, event):
  66         default_font = wx.Font(10, wx.SWISS , wx.NORMAL, wx.NORMAL, False, "Verdana")
  67         data = wx.FontData()
  68         if sys.platform == 'win32':
  69             data.EnableEffects(True)
  70         data.SetAllowSymbols(False)
  71         data.SetInitialFont(default_font)
  72         data.SetRange(10, 30)
  73         dlg = wx.FontDialog(self, data)
  74         if dlg.ShowModal() == wx.ID_OK:
  75             data = dlg.GetFontData()
  76             font = data.GetChosenFont()
  77             color = data.GetColour()
  78             text = 'Face: %s, Size: %d, Color: %s' % (font.GetFaceName(), font.GetPointSize(),  color.Get())
  79             self.SetStatusText(text)
  80         dlg.Destroy()
  81 
  82     def opendir(self, event):
  83         dlg = wx.DirDialog(self, "Choose a directory:", style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
  84         if dlg.ShowModal() == wx.ID_OK:
  85             self.SetStatusText('You selected: %s\n' % dlg.GetPath())
  86         dlg.Destroy()
  87 
  88     def singlechoice(self, event):
  89         sins = ['Greed', 'Lust', 'Gluttony', 'Pride', 'Sloth', 'Envy', 'Wrath']
  90         dlg = wx.SingleChoiceDialog(self, 'Seven deadly sins', 'Which one?', sins, wx.CHOICEDLG_STYLE)
  91         if dlg.ShowModal() == wx.ID_OK:
  92             self.SetStatusText('You chose: %s\n' % dlg.GetStringSelection())
  93         dlg.Destroy()
  94 
  95     def textentry(self, event):
  96         dlg = wx.TextEntryDialog(self, 'Enter some text','Text Entry')
  97         dlg.SetValue("Default")
  98         if dlg.ShowModal() == wx.ID_OK:
  99             self.SetStatusText('You entered: %s\n' % dlg.GetValue())
 100         dlg.Destroy()
 101 
 102 class MyApp(wx.App):
 103     def OnInit(self):
 104         myframe = MyFrame(None, -1, "commondialogs.py")
 105         myframe.CenterOnScreen()
 106         myframe.Show(True)
 107         return True
 108 
 109 app = MyApp(0)
 110 app.MainLoop()

The script shows eight different common dialogs. All the output is displayed on the statusbar.

messagedialog.png textentrydialog.png

fontdialog.png colordialog.png

directorydialog.png filedialog.png

pagesetupdialog.png singlechoicedialog.png

Core Widgets

In this section, we will introduce basic widgets in wxPython. Each widget will have a small code example.

wx.Button

wx.Button is a simple widget. It contains a text string. It is used to trigger an action.

wx.Button styles

wx.Button methods

SetDefault()

set the button to be the default item on a window

wx.Size GetDefaultSize()

get the default button size on a platform

   1 #!/usr/bin/python
   2 
   3 # buttons.py
   4 
   5 import wx
   6 import random
   7 
   8 APP_SIZE_X = 300
   9 APP_SIZE_Y = 200
  10 
  11 class MyButtons(wx.Dialog):
  12     def __init__(self, parent, id, title):
  13         wx.Dialog.__init__(self, parent, id, title, size=(APP_SIZE_X, APP_SIZE_Y))
  14 
  15         wx.Button(self, 1, 'Close', (50, 130))
  16         wx.Button(self, 2, 'Random Move', (150, 130), (110, -1))
  17 
  18         self.Bind(wx.EVT_BUTTON, self.OnClose, id=1)
  19         self.Bind(wx.EVT_BUTTON, self.OnRandomMove, id=2)
  20 
  21         self.Centre()
  22         self.ShowModal()
  23         self.Destroy()
  24 
  25     def OnClose(self, event):
  26         self.Close(True)
  27 
  28     def OnRandomMove(self, event):
  29         screensize = wx.GetDisplaySize()
  30         randx = random.randrange(0, screensize.x - APP_SIZE_X)
  31         randy = random.randrange(0, screensize.y - APP_SIZE_Y)
  32         self.Move((randx, randy))
  33 
  34 app = wx.App(0)
  35 MyButtons(None, -1, 'buttons.py')
  36 app.MainLoop()

buttons.png

Figure: buttons.py

wx.ToggleButton

wx.ToggleButton is a button that has two states. Pressed and not pressed. You toggle between these two states by clicking on it. There are situations where this functionality fits well.

wx.ToggleButton methods

SetValue(bool value)

set a state of a toggle button

bool GetValue()

get a state of the toggle button

SetLabel(string label)

set a label for the button

   1 #!/usr/bin/python
   2 
   3 # togglebuttons.py
   4 
   5 import wx
   6 
   7 class ToggleButtons(wx.Dialog):
   8     def __init__(self, parent, id, title):
   9         wx.Dialog.__init__(self, parent, id, title, size=(300, 200))
  10 
  11         self.colour = wx.Colour(0, 0, 0)
  12 
  13         wx.ToggleButton(self, 1, 'red', (20, 25))
  14         wx.ToggleButton(self, 2, 'green', (20, 60))
  15         wx.ToggleButton(self, 3, 'blue', (20, 100))
  16 
  17         self.panel  = wx.Panel(self, -1, (150, 20), (110, 110), style=wx.SUNKEN_BORDER)
  18         self.panel.SetBackgroundColour(self.colour)
  19 
  20         self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleRed, id=1)
  21         self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleGreen, id=2)
  22         self.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleBlue, id=3)
  23 
  24         self.Centre()
  25         self.ShowModal()
  26         self.Destroy()
  27 
  28     def ToggleRed(self, event):
  29         green = self.colour.Green()
  30         blue = self.colour.Blue()
  31         if  self.colour.Red():
  32             self.colour.Set(0, green, blue)
  33         else:
  34             self.colour.Set(255, green, blue)
  35         self.panel.SetBackgroundColour(self.colour)
  36 
  37     def ToggleGreen(self, event):
  38         red = self.colour.Red()
  39         blue = self.colour.Blue()
  40         if  self.colour.Green():
  41             self.colour.Set(red, 0, blue)
  42         else:
  43             self.colour.Set(red, 255, blue)
  44         self.panel.SetBackgroundColour(self.colour)
  45 
  46     def ToggleBlue(self, event):
  47         red = self.colour.Red()
  48         green = self.colour.Green()
  49         if  self.colour.Blue():
  50             self.colour.Set(red, green, 0)
  51         else:
  52             self.colour.Set(red, green, 255)
  53         self.panel.SetBackgroundColour(self.colour)
  54 
  55 
  56 app = wx.App(0)
  57 ToggleButtons(None, -1, 'togglebuttons.py')
  58 app.MainLoop()

togglebuttons.png

Figure: togglebuttons.py

wx.BitmapButton

A bitmap button is a button, that displays a bitmap. A bitmap button can have three other states. Selected, focused and displayed. We can set a specific bitmap for those states.

wx.BitmapButton methods

wx.Bitmap GetBitmapLabel()

return the label bitmap

SetBitmapLabel(wx.Bitmap bitmap)

set the bitmap label for the button

wx.Bitmap GetBitmapFocus()

return the bitmap for the focused state

wx.Bitmap GetBitmapDisabled()

return the bitmap for the disabled state

wx.Bitmap GetBitmapSelected()

return the bitmap for the selected state

SetBitmapFocus(wx.Bitmap bitmap)

set the bitmap for the focused state

SetBitmapSelected(wx.Bitmap bitmap)

set the bitmap for the selected state

SetBitmapDisabled(wx.Bitmap bitmap)

set the bitmap for the disabled state

SetMargins(int x, int y)

not implemented

int GetMarginX()

not implemented

int GetMarginY()

not implemented

A video player is a nice example, where bitmap buttons are used. We can see play, pause, next, previous and volume bitmap buttons there. So we create a skeleton of a video player in our next example.

   1 #!/usr/bin/python
   2 
   3 # player.py
   4 
   5 import wx
   6 
   7 class MyFrame(wx.Frame):
   8     def __init__(self, parent, id, title):
   9         wx.Frame.__init__(self, parent, id, title, size=(350, 300))
  10         panel = wx.Panel(self, -1)
  11 
  12         pnl1 = wx.Panel(self, -1)
  13         pnl1.SetBackgroundColour(wx.BLACK)
  14         pnl2 = wx.Panel(self, -1 )
  15 
  16         menubar = wx.MenuBar()
  17         file = wx.Menu()
  18         play = wx.Menu()
  19         view = wx.Menu()
  20         tools = wx.Menu()
  21         favorites = wx.Menu()
  22         help = wx.Menu()
  23 
  24         file.Append(101, '&quit', 'Quit application')
  25 
  26         menubar.Append(file, '&File')
  27         menubar.Append(play, '&Play')
  28         menubar.Append(view, '&View')
  29         menubar.Append(tools, '&Tools')
  30         menubar.Append(favorites, 'F&avorites')
  31         menubar.Append(help, '&Help')
  32 
  33         slider1 = wx.Slider(pnl2, -1, 0, 0, 1000)
  34         pause = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-pause.png'))
  35         play  = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-play.png'))
  36         next  = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-next.png'))
  37         prev  = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/stock_media-prev.png'))
  38         volume = wx.BitmapButton(pnl2, -1, wx.Bitmap('icons/volume.png'))
  39         slider2 = wx.Slider(pnl2, -1, 0, 0, 100, size=(120, -1))
  40 
  41         vbox = wx.BoxSizer(wx.VERTICAL)
  42         hbox1 = wx.BoxSizer(wx.HORIZONTAL)
  43         hbox2 = wx.BoxSizer(wx.HORIZONTAL)
  44 
  45         hbox1.Add(slider1, 1)
  46         hbox2.Add(pause)
  47         hbox2.Add(play, flag=wx.RIGHT, border=5)
  48         hbox2.Add(next, flag=wx.LEFT, border=5)
  49         hbox2.Add(prev)
  50         hbox2.Add((150, -1), 1, flag=wx.EXPAND | wx.ALIGN_RIGHT)
  51         hbox2.Add(volume, flag=wx.ALIGN_RIGHT)
  52         hbox2.Add(slider2, flag=wx.ALIGN_RIGHT | wx.TOP | wx.LEFT, border=5)
  53 
  54         vbox.Add(hbox1, 1, wx.EXPAND | wx.BOTTOM, 10)
  55         vbox.Add(hbox2, 1, wx.EXPAND)
  56         pnl2.SetSizer(vbox)
  57 
  58         sizer = wx.BoxSizer(wx.VERTICAL)
  59         sizer.Add(pnl1, 1, flag=wx.EXPAND)
  60         sizer.Add(pnl2, flag=wx.EXPAND | wx.BOTTOM | wx.TOP, border=10)
  61 
  62         self.SetMinSize((350, 300))
  63         self.SetMenuBar(menubar)
  64         self.CreateStatusBar()
  65         self.SetSizer(sizer)
  66         self.Centre()
  67 
  68 class MyApp(wx.App):
  69     def OnInit(self):
  70         frame = MyFrame(None, -1, 'Player')
  71         frame.Show(True)
  72         self.SetTopWindow(frame)
  73         return True
  74 
  75 app = MyApp(0)
  76 app.MainLoop()

player.png

Figure: player.py

wx.StaticLine

This widget displays a simple line on the window. It can be horizontal or vertical.

wx.StaticLine styles

wx.StaticLine methods

bool IsVertical()

check if the line is vertical

integer GetDefaultSize()

return the size of the line

   1 #!/usr/bin/python
   2 
   3 # centraleurope.py
   4 
   5 import wx
   6 
   7 class MyDialog(wx.Dialog):
   8     def __init__ (self, parent, ID, title):
   9         wx.Dia