Chapter 28
Most software written today uses a graphical user interface (GUI) rather than the text-based interface we have used in most of our examples. In this chapter, we rewrite the Chuck-a-Luck program from Chapter 24 to use a graphical interface.
Listing 28.1: Chuck-a-Luck (GUI Version)
1 # chuckgui.py
2
3 from dicegui import DiceGUI
4 from tkinter import *
5
6 class ChuckALuckGUI(Frame):
7 def __init__(self, parent):
8 Frame.__init__(self, parent)
9 parent.geometry("500×300")
10 parent.title("Chuck-A-Luck")
11 self.dice = DiceGUI(3, self)
12 self.dice.pack(fill="y", expand=True)
13 self.score = 0
14 self.makecontrols()
15 self.makescore()
16 self.pack(fill="y", expand=True)
17
18 def makecontrols(self):
19 self.choice = IntVar()
20 controlframe = Frame(self)
21 for i in range(1, 7):
22 Radiobutton(controlframe, text=str(i), value=i,
23 variable=self.choice).pack(side="left")
24 Label(controlframe, width=5).pack(side="left")
25 Button(controlframe, width=10, text="Roll",
26 command=self.roll).pack(side="left")
27 controlframe.pack()
28
29 def makescore(self):
30 self.scorestr = StringVar()
31 Label(self, textvariable=self.scorestr,
32 font=("Helvetica", 24)).pack(fill="y", expand=True)
33 self.updatescore()
34
35 def roll(self):
36 self.dice.rollall()
37 matches = self.dice.count(self.choice.get())
38 self.score += matches if matches > 0 else − 1
39 self.updatescore()
40
41 def updatescore(self):
42 self.scorestr.set("Score: " + str(self.score))
43
44 root = Tk()
45 app = ChuckALuckGUI(root)
46 root.mainloop()
Be aware that the GUI window created by this program may be hidden behind other windows when you run it. We also need GUI code for the dice.
Listing 28.2: Dice GUI
1 # dicegui.py
2
3 from dice import Dice
4 from tkinter import *
5
6 class DiceGUI(Frame, Dice):
7 def __init__(self, n, parent):
8 Frame.__init__(self, parent)
9 Dice.__init__(self, n)
10 self.loadimages()
11 self.n = n
12 self.labels = [Label(self) for i in range(self.n)]
13 for i in range(self.n):
14 self.setimage(i)
15 self.labels[i].pack(side="left")
16
17 def loadimages(self):
18 self.images = [None] * 7
19 for i in range(1, 7):
20 fname = "die" + str(i) + ".ppm"
21 self.images[i] = PhotoImage(file=fname)
22
23 def rollall(self):
24 Dice.rollall(self)
25 for i in range(self.n):
26 self.setimage(i)
27
28 def setimage(self, i):
29 self.labels[i].config(image=self.images[int(self.dice[i])])
The tkinter module is one of the standard libraries for developing graphical applications in Python. It provides an interface to Tk, which is a crossplatform GUI library for the Tcl language. Because a typical GUI application uses many components from tkinter, the *-form of the import is common.
The Tk class from the tkinter module creates the main window for a GUI application. Once a Tk object has been created, then we just add GUI components (called “widgets”) to it and finish with a call to the Tk object’s .mainloop() method.
In order to understand the .mainloop() method, we need to discuss eventdriven programming. A GUI application needs to respond to user events such as mouse-clicks, pushing buttons, and choosing menu items. In order to do this, the program goes into an event loop:
while True:
# get next event
# process event
Given a Tk object named root, these two methods control the event loop:
root.mainloop() |
Start main event loop. |
root.quit() |
Stop main event loop. |
The event loop is also halted if the user closes the main window of the application.
Listing 28.1 uses two other methods that are available on all tkinter windows, including the root Tk object:
win.geometry("widthxheight") |
Set size of window. |
win.title(str) |
Set window title to str. |
GUI components in tkinter are known as widgets. Listings 28.1 and 28.2 use the following widget classes:
Frame |
Group widgets for layout. |
Label |
Display text or image. |
Button |
Single button. |
Radiobutton |
Button allowing only one from a group to be selected at a time. Buttons with same variable form a group. |
The first parameter to a widget’s constructor is always the GUI component that will contain the widget, known as its parent. The new widget is then a child of the parent. Other parameters are assigned values with keyword arguments, which have the form
keyword=value
The advantage of using keywords in this case is that the caller does not need to list every parameter in the precisely correct order. Instead, only those parameters being used are specified, and they may be listed in any order.
Most of the keyword arguments used to set widget options in Listings 28.1 and 28.2 are self-explanatory. For example, the command parameter of the Button widget specifies the command to execute when the button is clicked. Check the tkinter documentation if you have questions and to see additional options.
All of the options that can be set via keyword arguments in widget constructors may also be set later with the .config() method:
widget.config(keyword=value, ...) |
Set widget options. |
or by setting dictionary values:
widget["keyword"]=value |
Set widget option. |
The keyword must be in quotes when using the dictionary syntax.
The tkinter module provides its own special variable classes to use with those widgets that require variables in order to communicate data either to or from the rest of a Python program. For example, the set of Radiobutton objects uses the variable self.choice to inform the .roll() method which radio button was chosen. An ordinary Python variable cannot be used for this purpose; instead, an IntVar is created. Similarly, a StringVar is used by the score Label to update and display the score.
If v is either an IntVar or StringVar, these methods are used to access or modify its contents:
v.get() |
Get value of v. |
v.set(value) |
Set v to value. |
GUI applications need some way to describe and define how their components should be organized onscreen. The tkinter module provides three different geometry managers for this purpose: the Pack manager organizes components into groups, the Grid manager places them in “row, column” positions, and the Place manager allows precise pixel locations. The Pack manager and Grid manager are probably used the most often; Listing 28.1 uses the Pack manager for its layout.
⇒ Caution: Never use more than one geometry manager in a single window.
The basic idea of the Pack manager is that each component calls its .pack() method to describe how it should be placed within its parent.
widget.pack(keyword=value, ...) |
Add widget to its parent with |
|
keyword options. |
Some of the .pack() method options are:
side="left", "right", "top", or "bottom" Pack this widget into its parent by sliding it up against the specified side. Default is top.
fill=None, "x", "y", or "both" If None, the widget keeps its original size. Filling in the x or y directions (or both) means the widget will use all of the space given to it by the parent in the specified direction(s). Default is None.
expand=False or True If True, this widget is willing to expand to take extra space available in the parent. Default is False.
Frames are often used with the Pack manager to group objects together in the layout. Generally, one set of options is used to pack widgets into a Frame, and then the Frame specifies how it should be packed into its parent.
⇒ Caution: When using the Pack manager, components will not appear until you call their .pack() method.
Take a look at line 6 of Listing 28.2: it appears that the DiceGUI class inherits from both Frame and Dice. This mechanism is called multiple inheritance, and it does exactly what you expect: the DiceGUI class inherits state and behavior from both the Frame and Dice classes. A DiceGUI instance is a Frame and a set of Dice.
Notice a couple of details in the code: first, in the __init__() method, both superclass __init__() methods are called. Secondly, the DiceGUI class overrides the definition of rollall() it inherited from Dice. The old version is called as part of the new definition in line 24:
Dice.rollall(self)
This allows the DiceGUI class to extend the behavior of the original method.
Finally, the tkinter module allows using GIF or raw binary PPM images inside GUI applications with the PhotoImage class:
PhotoImage(file=fname) |
Open image in fname. |
Inside a tkinter program, PhotoImage objects may be used anywhere a widget expects an image.
In Listing 28.2, a PhotoImage is created for each face of the die and stored in the images field of the DiceGUI class. Then a call to the Label .config() method allows us to update the image any time the dice have been rolled.
import <module> as <alias>
Modify Listing 28.1 to use the alias tk for tkinter instead of importing all names from the tkinter module. Discuss the tradeoffs. (You do not need to modify Listing 28.2.)
3.147.126.242