![]() |
||||
|
||||
|
« It's My Merchandise and Services, It's My Money, or Is It? | Linux on Dell Precision M90 - Part VII: Peripherals » |
GUI design in Python
by: crystalattice - Jun 03, 2006
Many times, making a Graphical User Interface (GUI) for a program can be a pain. You have to either mess around with Microsoft products (e.g. Visual Studio), you have to find a useful GUI designer (e.g. Qt or FLTK), or you have to figure out how to hand code the GUI. Python is somewhat unique in that, not only can you use pre-built, cross-platform libraries such as wxGlade, you can also just use Tkinter, the Tk/TCL-based GUI library that comes with every Python language download. For my Colonial Marines RPG, one of the first things I had to program was a dice rolling module. (I could have found something already created, but wanted the Python practice). The dice roller program works great, but it was getting to be a pain to have to retype dice rolls on the command line. Even creating new functions that implemented various rolls needed didn't help very much. Since it's easier to click a mouse button once vs. typing several keys, I decided to take the time to learn how to make a GUI, especially since I'll eventually have to do it for much of my game. And since I needed to do it, I thought I'd do it right; at least, I'd figure which GUI library gave the best results. To this end I decided to make a dice rolling GUI using Python's built-in Tkinter library and the optional download of wxPython (actually implemented via wxGlade). For those interested in "playing along at home", I'll include the dice roller module below so you can import it into the GUI program. Python Code Example: ############################################################# #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 #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() So, the first GUI I created was in Tkinter. As I mentioned, Tkinter is included with the Python language, so all you have to do to use it is just add from Tkinter import *, which not only imports all classes from Tkinter, but it lets you use these classes without having to qualify them, like button1 = Tkinter.Button(). One of the problems with Tkinter is that it gives you several methods to create the GUI, i.e. you get different visual presentations depending on if you use pack, grid, or place. Originally I tried using the default pack but it's difficult to make a nice looking interface unless you want the typical top-down look. For my purposes, I wanted a 3x3 grid but pack places the widgets in the frame as they are listed in the code. Grid gives you more control and obviously place gives you the most; your best bet is usually grid unless you aren't a control freak and don't mind tweaking the pack settings. Below is the code for a dice rolling simulation GUI using Tkinter: Python Code Example: ################################################################ #Dice_roller_GUI # #Purpose: Provide a graphical interface to the dice_roller module; # allow various die rolls using the mouse. This program uses the Tkinter # GUI module that is included with Python. Requires Python 2.3+ to be # installed. #Author: Cody Jackson #Date: 6/1/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 ################################################################# from Tkinter import * from dice_roller import multiDie class DiceRoll(Frame): """Dice rolling simulation program.""" def __init__(self): #---Frame is the "container" for rest of code Frame.__init__(self) self.pack(expand = YES, fill = BOTH) #Frame fills all available space self.master.title("Dice roll simulation") #---Create buttons for each type of die roll self.button1d6 = Button(self, text = "1d6", command = self.pressed1d6) self.button1d6.grid(row = 0, column = 0) #stack d6 buttons vertically self.button2d6 = Button(self, text = "2d6", command = self.pressed2d6) self.button2d6.grid(row = 1, column = 0) self.button3d6 = Button(self, text = "3d6", command = self.pressed3d6) self.button3d6.grid(row = 2, column = 0) self.button1d10 = Button(self, text = "1d10", command = self.pressed1d10) self.button1d10.grid(row = 0, column = 1) #stack d10 buttons next to d6 buttons self.button2d10 = Button(self, text = "2d10", command = self.pressed2d10) self.button2d10.grid(row = 1, column = 1) self.button1d100 = Button(self, text = "1d100 (%)", command = self.pressed1d100) self.button1d100.grid(row = 2, column = 1) #---Create message area self.result = StringVar() self.resultLine = Label(self, textvariable = self.result) self.result.set("Die roll") self.resultLine.grid(row = 1, column = 2) #---Define button methods def pressed1d6(self): """Roll one 6-sided die.""" self.result.set(multiDie(1,1)) def pressed2d6(self): """Roll two 6-sided dice.""" self.result.set(multiDie(2,1)) def pressed3d6(self): """Roll three 6-sided dice.""" self.result.set(multiDie(3,1)) def pressed1d10(self): """Roll one 10-sided die.""" self.result.set(multiDie(1,2)) def pressed2d10(self): """Roll two 10-sided dice.""" self.result.set(multiDie(2,2)) def pressed1d100(self): """Roll one 100-sided die or roll a percentage.""" self.result.set(multiDie(1,3)) def main(): DiceRoll().mainloop() if __name__ == "__main__": main() The Tkinter code is all hand-made; I'm not aware of any graphical way to place Tkinter widgets. But as you can see, it's not too bad to hand code, unless your application is going to be large. wxPython is a GUI tookit that uses the wxWidgets library as implemented with Python. wxGlade is a GUI designer using wxPython to create GUI's. wxGlade can be used separately or you can use it via the SPE Python IDE. SPE is what I used to make this wxPython-version of the dice rolling GUI. One thing to note is that wxPython/wxGlade are separate programs that aren't included with the Python language; if you want to use the wx* programs or even to use SPE, you have to install wxPython and wxGlade. Python Code Example: ################################################################ #diceRollSim.py # #Purpose: Provide a graphical interface to the dice_roller module; # allow various die rolls using the mouse. This program uses the wxGlade # GUI module. Requires wxPython 2.6+ to be installed in addition to # Python 2.3+. #Author: Cody Jackson #Date: 6/1/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 ################################################################# #!/usr/bin/env python # -*- coding: ISO-8859-1 -*- # generated by wxGlade 0.4 on Thu Jun 01 13:01:09 2006 import wx from dice_roller import multiDie class MyFrame(wx.Frame): """Container for dice roller window.""" def __init__(self, *args, **kwds): """Creates dice roller panel with buttons and output field.""" # begin wxGlade: MyFrame.__init__ kwds["style"] = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.panel_1 = wx.Panel(self, -1) self.Title = wx.StaticText(self.panel_1, -1, "Dice roll simulator") self.roll_output = wx.TextCtrl(self.panel_1, -1, "") self.button_1d6 = wx.Button(self.panel_1, -1, "1d6") self.button_1d10 = wx.Button(self.panel_1, -1, "1d10") self.button_2d6 = wx.Button(self.panel_1, -1, "2d6") self.button_2d10 = wx.Button(self.panel_1, -1, "2d10") self.button_3d6 = wx.Button(self.panel_1, -1, "3d6") self.button_d100 = wx.Button(self.panel_1, -1, "d100 (%)") self.__set_properties() self.__do_layout() # end wxGlade #---Button events wx.EVT_BUTTON(self, self.button_1d6.GetId(), self.pressed1d6) wx.EVT_BUTTON(self, self.button_2d6.GetId(), self.pressed2d6) wx.EVT_BUTTON(self, self.button_3d6.GetId(), self.pressed3d6) wx.EVT_BUTTON(self, self.button_1d10.GetId(), self.pressed1d10) wx.EVT_BUTTON(self, self.button_2d10.GetId(), self.pressed2d10) wx.EVT_BUTTON(self, self.button_d100.GetId(), self.pressed1d100) #---Define button methods def pressed1d6(self, event): """Roll one 6-sided die.""" self.roll_output.SetValue("") #clears any value in text box val = str(multiDie(1,1)) self.roll_output.SetValue(val) def pressed2d6(self, event): """Roll two 6-sided dice.""" self.roll_output.SetValue("") val = str(multiDie(2,1)) self.roll_output.SetValue(val) def pressed3d6(self, event): """Roll three 6-sided dice.""" self.roll_output.SetValue("") val = str(multiDie(3,1)) self.roll_output.SetValue(val) def pressed1d10(self, event): """Roll one 10-sided die.""" self.roll_output.SetValue("") val = str(multiDie(1,2)) self.roll_output.SetValue(val) def pressed2d10(self, event): """Roll two 10-sided dice.""" self.roll_output.SetValue("") val = str(multiDie(2,2)) self.roll_output.SetValue(val) def pressed1d100(self, event): """Roll one 100-sided die or roll a percentage.""" self.roll_output.SetValue("") val = str(multiDie(1,3)) self.roll_output.SetValue(val) def __set_properties(self): """Sets the visual presentation values of the window.""" # begin wxGlade: MyFrame.__set_properties self.SetTitle("Dice simulator") self.Title.SetFont(wx.Font(11, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, "")) self.roll_output.SetFont(wx.Font(11, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) self.button_1d6.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) self.button_1d10.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) self.button_2d6.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) self.button_2d10.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) self.button_3d6.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) self.button_d100.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) # end wxGlade def __do_layout(self): """Sets the visual layout of window.""" # 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.Title, 0, wx.ADJUST_MINSIZE, 0) grid_sizer_1.Add(self.roll_output, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) grid_sizer_1.Add(self.button_1d6, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) grid_sizer_1.Add(self.button_1d10, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) grid_sizer_1.Add(self.button_2d6, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) grid_sizer_1.Add(self.button_2d10, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) grid_sizer_1.Add(self.button_3d6, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) grid_sizer_1.Add(self.button_d100, 0, wx.EXPAND|wx.ADJUST_MINSIZE, 0) self.panel_1.SetAutoLayout(True) self.panel_1.SetSizer(grid_sizer_1) grid_sizer_1.Fit(self.panel_1) grid_sizer_1.SetSizeHints(self.panel_1) sizer_1.Add(self.panel_1, 1, wx.EXPAND, 0) self.SetAutoLayout(True) self.SetSizer(sizer_1) sizer_1.Fit(self) sizer_1.SetSizeHints(self) self.Layout() # end wxGlade # end of class MyFrame class MyDice(wx.App): """Makes an instance of MyFrame.""" def OnInit(self): """Initializes all settings for an instance.""" wx.InitAllImageHandlers() diceRollSim = MyFrame(None, -1, "") self.SetTopWindow(diceRollSim) diceRollSim.Show() return 1 # end of class MyDice if __name__ == "__main__": Dice_roll = MyDice(0) Dice_roll.MainLoop() There's a lot more code in this because wxGlade makes the code for you. It's a little more complicated to use, mostly because the wxGlade documentation is somewhat sparse. But the SPE site does have links to several tutorials which will help out immensely. Here's what the Tkinter GUI looks like on my Mac: Here is the GUI as developed in wxGlade: I leave it up to you as to which is better. Be aware that you can tweak the settings for either one to make them look more alike; I just chose not to. Popular consensus states wxPython is better because it uses the native OS widgets better than Tkinter and it has more widgets to choose from. However, Tkinter doesn't require a separate program to install and it's reasonably easy to learn. Either way, making GUI's in Python is actually fairly easy, once you learn how the libraries work.
|
GIDNetwork Sites
Archives
Recent GIDBlog Posts
Recent GIDForums Posts
Contact Us
|
« It's My Merchandise and Services, It's My Money, or Is It? | Linux on Dell Precision M90 - Part VII: Peripherals » |