Chapter 28

Graphical User Interfaces

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

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.

Event-Driven Programming

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.

Window Methods

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.

Widgets

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.

Widget Options

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.

IntVar and StringVar

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.

Geometry Managers

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.

Multiple Inheritance

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.

PhotoImages

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.

Exercises

  1. 28.1 Use Listings 28.1 and 28.2 to answer these questions:
    1. (a) List all instance variables in the ChuckALuckGUI class. Do not include those it inherits from Frame.
    2. (b) List all instance variables in the DiceGUI class. Do include those inherited from the Dice class (in Listing 23.1), but do not include those from Frame.
  2. 28.2 Use Listings 28.1 and 28.2 to answer these questions:
    1. (a) Explain how the radio buttons get the correct labels.
    2. (b) Find and explain the line of code that checks how many dice have the same value as the selected radio button.
    3. (c) Find and explain the line of code that updates the image of a die after it has been rolled.
  3. 28.3 Describe the purpose of each of these widgets in the makecontrols() method of Listing 28.1:
    1. (a) controlframe
    2. (b) The Label in line 24
  4. 28.4 Describe and explain the result of commenting out each of these lines of code from Listing 28.1:
    1. (a) Line 12
    2. (b) Line 16
    3. (c) Line 27
  5. 28.5 Use Listing 28.2 to answer these questions:
    1. (a) Explain the need for the assignment in line 18. Hint: what happens if that line is removed?
    2. (b) Explain why line 18 uses the value 7 instead of 6.
    3. (c) Consider alternative ways of writing the loadimages() method. Discuss the tradeoffs.
  6. 28.6 Modify Listing 28.1 to turn off all of the expand options. Describe and then explain the result.
  7. 28.7 Python offers the option of defining an alias for a module with
    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.)

  8. 28.8 Instead of making ChuckALuckGUI a subclass of Frame, an alternative design is to create a Frame object in __init()__ and store it in an instance variable of ChuckALuckGUI. This technique is called using composition instead of inheritance. Composition models a “has-a” relationship because, in this case, the application class has a Frame. Rewrite Listing 28.1 with this design. Discuss the tradeoffs.
  9. 28.9 Modify Listings 28.1 and 28.2 to display the dice in a vertical column on the right side of the window. Arrange the other components in a column on the left side of the window. Make whatever changes are necessary to produce a nice layout.
  10. 28.10 Research the tkinter Grid geometry manager and modify Listings 28.1 and 28.2 to use it instead of the Pack manager. Reproduce the layout of the original program as closely as possible.
  11. 28.11 Modify Listing 28.1 to improve the (very minimal) design so that it is more interesting and appealing.
  12. 28.12 Design and implement a GUI version of dice poker (see Exercise 25.8).
  13. 28.13 Design and implement a GUI application of your choice.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.126.242