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.
#Purpose: A random number generation program that simulates
# various dice rolls.
#Author: Cody Jackson
#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
# 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”
def multiDie ( dice_number, die_type ) :
“””Add die rolls together, e.g. 2d6, 4d10, etc.”””
final_roll = 0
val = 0
while val < dice_number:
final_roll += randomNumGen ( die_type )
val += 1
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.
# -*- coding: iso-8859-15 -*-
# generated by wxGlade 0.6.2 on Fri Aug 29 09:24:23 2008
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.