Developing GUIs with wxPython (Part 3)

A Couple Quickie Programs

Time for a long post. I’m going to show a simple wxPython program to highlight the various portions you should be familiar with. But first, I’m giving you a program that I wrote a few years ago so you can see how a command-line program can be easily converted to a GUI program.

The dice rolling program below is one of the first programs I wrote while I was learning Python. It’s simply a random number generator that allows the user to input the number of “dice” to roll and the number of “sides” of each die. I’m including the entire program to show you what I consider to be good code. It includes sufficient comments to explain what the program does, includes PyDoc statements (the statements in triple quotes), and it defines a simple test that is run when the program is called directly; the test ensure the program is working correctly and gives an example of what the program does.

You’ll also note the huge comment block at the beginning. Because this program is licensed under the GPL, it includes the standard GPL disclaimer. This section also includes the purpose of the program, the date it was created, and the current version.

#############################################################

#Dice_roller.py

#

#Purpose:  A random number generation program that simulates

#  various dice rolls.

#Author:  Cody Jackson

#Date:  4/11/06

#

#Copyright 2006 Cody Jackson

#This program is free software; you can redistribute it and/or modify it

#under the terms of the GNU General Public License as published by the Free

#Software Foundation; either version 2 of the License, or ( at your option )

#any later version.

#

#This program is distributed in the hope that it will be useful, but

#WITHOUT ANY WARRANTY; without even the implied warranty of

#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU

#General Public License for more details.

#

#You should have received a copy of the GNU General Public License

#along with this program; if not, write to the Free Software Foundation,

#Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

#———————————————————–

#Version 1.0

#   Initial build

#############################################################

import random #randint

def randomNumGen ( choice ) :

“””Get a random number to simulate a d6, d10, or d100 roll.”””

if choice == 1: #d6 roll

die = random.randint ( 1, 6 )

elif choice == 2: #d10 roll

die = random.randint ( 1, 10 )

elif choice == 3: #d100 roll

die = random.randint ( 1, 100 )

else:   #simple error message

print “Shouldn’t be here.  Invalid choice”

return die

def multiDie ( dice_number, die_type ) :

“””Add die rolls together, e.g. 2d6, 4d10, etc.”””

#—Initialize variables

final_roll = 0

val = 0

while val < dice_number:

final_roll += randomNumGen ( die_type )

val += 1

return final_roll

def test ( ) :

“””Test criteria to show script works.”””

_1d6 = multiDie ( 1, 1 )    #1d6

print “1d6 = “, _1d6,

_2d6 = multiDie ( 2, 1 )    #2d6

print “n2d6 = “, _2d6,

_3d6 = multiDie ( 3, 1 )    #3d6

print “n3d6 = “, _3d6,

_4d6 = multiDie ( 4, 1 )    #4d6

print “n4d6 = “, _4d6,

_1d10 = multiDie ( 1, 2 )    #1d10

print “n1d10 = “, _1d10,

_2d10 = multiDie ( 2, 2 )    #2d10

print “n2d10 = “, _2d10,

_3d10 = multiDie ( 2, 2 )    #3d10

print “n3d10 = “, _3d10,

_d100 = multiDie ( 1, 3 )    #d100

print “n1d100 = “, _d100,

if __name__ == “__main__”:  #run test ( ) if calling as a separate program

test ( )

The dice roller program above can be run simply from the command line (python dice_roller.py) or it can be imported to the Python interactive shell and used that way. However, it has to be called every time you want to use it because I didn’t code it with a loop to keep it active. Creating a GUI for it makes it easier for the user to interact with it.

Below is the program I created that turns the dice roller into a GUI. You’ll notice that there are comments in the program indicating that the program was generated by wxGlade. You want to make sure any additions or changes to the wxGlade-generated code is outside of the begin/end comments. Whenever you make changes to your program using wxGlade, the code within those sections is generated by wxGlade; any code you put in there will be overwritten.

Note: the following program isn’t fully functional. It displays the required information but only the 1d6 button works. The reader is encouraged to revise the code to make the other buttons to work.

#!/usr/bin/env python

# -*- coding: iso-8859-15 -*-

# generated by wxGlade 0.6.2 on Fri Aug 29 09:24:23 2008

import wx

from dice_roller import multiDie

# begin wxGlade: extracode

# end wxGlade

class MyFrame ( wx.Frame ) :

def __init__ ( self, *args, **kwds ) :

# begin wxGlade: MyFrame.__init__

kwds[“style”] = wx.DEFAULT_FRAME_STYLE

wx.Frame.__init__ ( self, *args, **kwds )

self.panel_1 = wx.Panel ( self, -1 )

self.label_1 = wx.StaticText ( self.panel_1, -1, “Dice Roll Simulator” )

self.text_ctrl_1 = wx.TextCtrl ( self.panel_1, -1, “” )

self.button_1 = wx.Button ( self.panel_1, -1, “1d6” )

self.button_2 = wx.Button ( self.panel_1, -1, “1d10” )

self.button_3 = wx.Button ( self.panel_1, -1, “2d6” )

self.button_4 = wx.Button ( self.panel_1, -1, “2d10” )

self.button_5 = wx.Button ( self.panel_1, -1, “3d6” )

self.button_6 = wx.Button ( self.panel_1, -1, “d100” )

self.__set_properties ( )

self.__do_layout ( )

self.Bind ( wx.EVT_BUTTON, self.pressed1d6, self.button_1 )

# end wxGlade

def __set_properties ( self ) :

# begin wxGlade: MyFrame.__set_properties

self.SetTitle ( “frame_1” )

# end wxGlade

def __do_layout ( self ) :

# begin wxGlade: MyFrame.__do_layout

sizer_1 = wx.BoxSizer ( wx.VERTICAL )

grid_sizer_1 = wx.GridSizer ( 4, 2, 0, 0 )

grid_sizer_1.Add ( self.label_1, 0, 0, 0 )

grid_sizer_1.Add ( self.text_ctrl_1, 0, 0, 0 )

grid_sizer_1.Add ( self.button_1, 0, 0, 0 )

grid_sizer_1.Add ( self.button_2, 0, 0, 0 )

grid_sizer_1.Add ( self.button_3, 0, 0, 0 )

grid_sizer_1.Add ( self.button_4, 0, 0, 0 )

grid_sizer_1.Add ( self.button_5, 0, 0, 0 )

grid_sizer_1.Add ( self.button_6, 0, 0, 0 )

self.panel_1.SetSizer ( grid_sizer_1 )

sizer_1.Add ( self.panel_1, 1, wx.EXPAND, 0 )

self.SetSizer ( sizer_1 )

sizer_1.Fit ( self )

self.Layout ( )

# end wxGlade

def pressed1d6 ( self, event ) :

“””Roll one 6-sided die.”””

self.text_ctrl_1.SetValue ( “” )    #clears any value in text box

val = str ( multiDie ( 1, 1 ) )

self.text_ctrl_1.SetValue ( val )

# end of class MyFrame

if __name__ == “__main__”:

app = wx.PySimpleApp ( 0 )

wx.InitAllImageHandlers ( )

frame_1 = MyFrame ( None, -1, “” )

app.SetTopWindow ( frame_1 )

frame_1.Show ( )

app.MainLoop ( )

Now we will walk through the wxDiceRoller program, discussing the various sections. Line numbers have been added to help identify specific portions.

1.    #!/usr/bin/env python

2.    # -*- coding: iso-8859-15 -*-

3.    # generated by wxGlade 0.6.2 on Fri Aug 29 09:24:23 2008

4.    import wx

5.    from dice_roller import multiDie

6.    # begin wxGlade: extracode

7.    # end wxGlade

Line 1 is the normal “she-bang” line added to Python programs for use in Unix-like operating systems. It simply points towards the location of the Python interpreter in the computer system.

Lines 2, 3, 6, and 7 are auto-generated by wxGlade. Lines 6 and 7 delineate any additional code you want your program to use; normally you won’t have anything here.

Lines 4 and 5 import necessary modules for the program. Obviously, wx is the library for wxPython; dice_roller is the program from above. Line 5 imports just the multiDie function from the dice_roller program, rather than the entire program.

8.    class MyFrame ( wx.Frame ) :

9.    def __init__ ( self, *args, **kwds ) :

10.    # begin wxGlade: MyFrame.__init__

11.    kwds[“style”] = wx.DEFAULT_FRAME_STYLE

12.    wx.Frame.__init__ ( self, *args, **kwds )

13.    self.panel_1 = wx.Panel ( self, -1 )

14.    self.label_1 = wx.StaticText ( self.panel_1, -1, “Dice Roll Simulator” )

15.    self.text_ctrl_1 = wx.TextCtrl ( self.panel_1, -1, “” )

16.    self.button_1 = wx.Button ( self.panel_1, -1, “1d6” )

17.    self.button_2 = wx.Button ( self.panel_1, -1, “1d10” )

18.    self.button_3 = wx.Button ( self.panel_1, -1, “2d6” )

19.    self.button_4 = wx.Button ( self.panel_1, -1, “2d10” )

20.    self.button_5 = wx.Button ( self.panel_1, -1, “3d6” )

21.    self.button_6 = wx.Button ( self.panel_1, -1, “d100” )

Line 8 creates the class that will create the GUI. wxGlade allows you specify the name for your program’s objects; the default for the class is MyFrame. The more complex your program, the more classes you will want to have in your program. However, wxGlade creates one class (normally) and simply populates it with the elements you add. This particular class is a child of the wx.Frame class, one of the most common arrangements you will use.

Line 9 initializes the class, defining initial values and creating the widgets that are seen by the user. It’s a standard Python initialization statement, accepting various arguments and keywords so you can pass in whatever information you need. The arguments for this program will be discussed later.

Line 10 is another default comment generated by wxGlade, simply telling you where the auto-generated code starts so you don’t “step” on it.

Line 11 is one of the keywords passed into the class, in this case causing the frame to be created in the default style, which adds several standard widgets such as minimize, maximize, and close buttons.

Line 12 is simply the initialization statement for the wx.Frame class. What you have essentially done is make an instance of wx.Frame by creating the MyFrame class. However, before MyFrame can be created/used, an instance of wx.Frame has to be created first.

Line 13 creates a panel within the frame. A panel is the most common item for placing widgets in. You can add widgets to the frame directly but using a panel adds certain inherent features, such as allowing the Tab key to cycle through fields and buttons.

Lines 14-21 simply add widgets to the panel. These particular widgets simply create the dice rolling form by creating a label, an output field, and the “dice” buttons.

22.    self.__set_properties ( )

23.    self.__do_layout ( )

24.    self.Bind ( wx.EVT_BUTTON, self.pressed1d6, self.button_1 )

25.    # end wxGlade

26.    def __set_properties ( self ) :

27.    # begin wxGlade: MyFrame.__set_properties

28.    self.SetTitle ( “frame_1” )

29.    # end wxGlade

30.    def __do_layout ( self ) :

31.    # begin wxGlade: MyFrame.__do_layout

32.    sizer_1 = wx.BoxSizer ( wx.VERTICAL )

33.    grid_sizer_1 = wx.GridSizer ( 4, 2, 0, 0 )

34.    grid_sizer_1.Add ( self.label_1, 0, 0, 0 )

35.    grid_sizer_1.Add ( self.text_ctrl_1, 0, 0, 0 )

36.    grid_sizer_1.Add ( self.button_1, 0, 0, 0 )

37.    grid_sizer_1.Add ( self.button_2, 0, 0, 0 )

38.    grid_sizer_1.Add ( self.button_3, 0, 0, 0 )

39.    grid_sizer_1.Add ( self.button_4, 0, 0, 0 )

40.    grid_sizer_1.Add ( self.button_5, 0, 0, 0 )

41.    grid_sizer_1.Add ( self.button_6, 0, 0, 0 )

42.    self.panel_1.SetSizer ( grid_sizer_1 )

43.    sizer_1.Add ( self.panel_1, 1, wx.EXPAND, 0 )

44.    self.SetSizer ( sizer_1 )

45.    sizer_1.Fit ( self )

46.    self.Layout ( )

47.    # end wxGlade

Lines 22 & 23 simply call their respective methods, which are explained below.

Line 24 binds the clicking of the 1d6 button to the event that calculates and returns the “die roll”.

Line 25 indicates the end of the auto-generated wxGlade code.

Lines 26-29 are the set_properties method. This method sets the properties of your program, in this case the title of the window that is created upon running the program.

Lines 30-47 are the do_layout method. This method actually places the various widgets within the window upon creation.

Line 32 creates a sizer, an object that holds other objects and can automatically resize itself as necessary. When using wxGlade, this sizer is automatically created when creating your frame.

Line 33 is a different type of sizer, this one making a grid. The buttons and other widgets are added to the grid in a sequencial fashion, starting in the top left cell. This is good to know when you are hand-coding a grid or trying to auto-populate a grid from a list. Lines 34-41 simply add the various widgets to the grid’s cells.

Lines 42 & 44 add the sizers to their containers, in this case the panel.

Line 45 calls the Fit method, which tells the object (sizer_1) to resize itself to match the minimum size it thinks it needs.

Line 46 layouts and displays the widgets when the window is created.

48.    def pressed1d6 ( self, event ) :

49.    “””Roll one 6-sided die.”””

50.    self.text_ctrl_1.SetValue ( “” )    #clears any value in text box

51.    val = str ( multiDie ( 1, 1 ) )

52.    self.text_ctrl_1.SetValue ( val )

This code block is the method that calculates the die roll and returns that value to the output field in the window.

53.    if __name__ == “__main__”:

54.    app = wx.PySimpleApp ( 0 )

55.    wx.InitAllImageHandlers ( )

56.    frame_1 = MyFrame ( None, -1, “” )

57.    app.SetTopWindow ( frame_1 )

58.    frame_1.Show ( )

59.    app.MainLoop ( )

This block comes from standard Python programming. This block tests whether the program is being imported into another program or is being run by itself. This gives the developer a chance to modify the program’s behavior based on how it is being executed.

In this case, if the program is being called directly, the code is configured to create an instance of the MyFrame class and run it. The MainLoop() method, when invoked, waits for user input and responds appropriately.

This “initial” program is quite long, longer than you would normally expect for an introduction program. Most other tutorials or introductory books would start out with a much smaller program (not more than 10-20 lines). I decided to use this program because the actual logic flow is quite simple; most of the code is taken up by widget creation and placement. It’s not only a functional and reasonably useful program, it shows a relatively wide range of wxPython code.

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

2 Responses to Developing GUIs with wxPython (Part 3)

  1. serendipity says:

    No indentation => major fail.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s