GIDNetwork > GUI design in Python
Register
« 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.

Would you like to comment? This story has been viewed 29,842 times.
« It's My Merchandise and Services, It's My Money, or Is It? Linux on Dell Precision M90 - Part VII: Peripherals »

__top__

Copyright © GIDNetwork™ 2001 - 2024

Another website by J de Silva

Page generated in : 0.00784 sec.