Skip to main content

Graphical User Interfaces | Core Python 3.8

In this rather short chapter, you’ll learn the basics of how to make graphical user interfaces (GUIs) for your Python programs—you know, windows with buttons and text fields and stuff like that. The de facto standard GUI toolkit for Python is Tkinter, which ships as part of the standard Python distribution. Several other toolkits are available, however. This has its advantages (greater freedom of choice) and drawbacks (others can’t use your programs unless they have the same GUI toolkit installed). Fortunately, there is no conflict between the various GUI toolkits available for Python, so you can install as many different GUI toolkits as you want.
     This tutorial gives a brief introduction to using Tkinter, and we’ll build on this in another Tutorial. Tkinter is easy to use, but there’s a lot to learn if you want to use all of its features. I’ll only scratch the surface here to get you going; for further details, you should consult the section on graphical user interfaces in the standard library reference. There you’ll find documentation for Tkinter, as well as links to sites with more in-depth information and suggestions for other GUI packages to use.

Building a Sample GUI Application 

To demonstrate using Tkinter, I will show you how to build a simple GUI application. Your task is to write a basic program that enables you to edit text files. We aren’t going to write a full-fledged text editor but instead stick to the essentials. After all, the goal is to demonstrate the basic mechanisms of GUI programming in Python. The requirements for this minimal text editor are as follows:
 •    It must allow you to open text files, given their file names.
 •    It must allow you to edit the text files.
 •    It must allow you to save the text files.
 •    It must allow you to quit.
When writing a GUI program, it’s often useful to draw a sketch of how you want it to look. Figure 12-1 shows a simple layout that satisfies the requirements for our text editor.

 A sketch of the text editor
The elements of the interface can be used as follows:
 •    Type a file name in the text field to the left of the buttons and click Open to  open a file. The text contained in the file is put in the text field at the bottom.
 •    You can edit the text to your heart’s content in the large text field.
 •    If and when you want to save your changes, click the Save button, which again uses the text field containing the file name and writes the contents of the large text field to the file.
 •    There is no Quit button—we’ll just use the Quit command from the default Tkinter menus.
This might seem like a slightly daunting task, but it’s really a piece of cake.

Initial Exploration 

To begin with, you must import tkinter. To keep its namespace separate but save some typing, you might want to rename it.

import tkinter as tk

There’s not much harm in just importing all of its contents, though, if you prefer. For some initial exploration, let’s just use the interactive interpreter.

>>> from tkinter import *

To start up the GUI, we can create a top-level component, or widget, which will act as our main window. We do this by instantiating a Tk object.

>>> top = Tk()

At this point, a window should appear. In an ordinary program, we would insert a call to the function mainloop here to enter into the Tkinter main event loop, rather than simply exiting the program. There’s no need for that in the interactive interpreter, but feel free to try.

>>> mainloop()

The interpreter will seem to hang while the GUI is still working. To keep going, quit the GUI and restart the interpreter. Various widgets are available under rather obvious names. For example, to create a button, you instantiate the Button class. If there is no Tk instance, creating a widget will also instantiate Tk, so you can just jump right in.

>>> from tkinter import * 

>>> btn = Button()

The button won’t be visible at this point—you need to use a layout manager (also known as a geometry manager) to tell Tkinter where to place it. We’ll be using the pack manager, which in its simplest form simply involves calling the pack method.

>>> btn.pack()

Widgets have various properties we can use to modify their appearance and behavior. The properties are available like dictionary fields, so if we want to give our button some text, all it takes is an assignment.

>>> btn['text'] = 'Click me!'

By now, you should have a window looking somewhat like this:
Adding some behavior to the button is also quite straightforward.

>>> def clicked(): 

...

     print('I was clicked!') 

... 

>>> btn['command'] = clicked

If you click the button now, you should see the message printed out. Rather than individual assignments, you can use the config method to set several properties at once.

>>> btn.config(text='Click me!', command=clicked)

You can also configure the widget using its constructor.

>>> Button(text='Click me too!', command=clicked).pack()

Layout 

When we call pack on a widget, it is laid out within its parent widget, or master. The master widget may be supplied as an optional first argument to the constructor; if we don’t supply one, the main top-level window is used, as in the following snippet:

Label(text="I'm in the first window!").pack() 

second = Toplevel() 

Label(second, text="I'm in the second window!").pack()

The Toplevel class represents a top-level window beyond the main one, and Label is simply a text label. Without any parameters, pack will simply stack widgets in a single, centered column, starting at the top of the window. For example, the following will result in a tall, thin window with a single column of buttons:

for i in range(10):

    Button(text=i).pack()

Luckily, you can adjust the positioning and stretching of your widgets. The side you pack a widget on is given by the side parameter, to which you supply LEFT, RIGHT, TOP, or BOTTOM. If you want the widget to fill out the space assigned to it in the x- or y-direction, you specify a fill value of X, Y, or BOTH. If you want it to grow as the parent (in this case, the window) grows, you can set expand to true. There are other options as well, for specifying anchoring and padding, though I won’t be using them here. To get a quick overview, you can use the following:

>>> help(Pack.config)

There are other layout manager options, which might suit your taste better, namely, grid and place. You call these methods on the widgets you’re laying out, just like with pack. To avoid trouble, you should stick to a single layout manager for one container, such as a window. The grid method lets you lay out objects by placing them in the cells of an invisible table; you do this by specifying a row and column and possibly a rowspan or columnspan, if the widgets span multiple rows or columns. The place method lets you place widgets manually, by specifying the coordinates x and y, and the widgets’ height and weight. This is rarely a good idea but might be needed on occasion. Both of these geometry managers have additional parameters as well, which you can find by using the following:

>>> help(Grid.configure) 

>>> help(Place.config)

Event Handling 

As you’ve seen, we can supply an action for a button to take by setting the command property. This is a specialized form of event handling, for which Tkinter also has a more general mechanism: the bind method. You call this on the widget you want to handle a given kind of event, specifying the name of the event and a function to use. Here’s an example:

>>> from tkinter import * 

>>> top = Tk() 

>>> def callback(event):

 ...

     print(event.x, event.y)

 ... 

>>> top.bind('<Button-1>', callback) 

'4322424456callback'

Here, <Button-1> is the name for a mouse click (or equivalent) using the left button (button 1). We bind it to the callback function, which is called whenever we click inside the top window. An event object is passed along to the callback, and it has various properties depending on the kind of event. For a mouse click, for example, it provides the x and y coordinates, which are printed in this example. Many other kinds of events are available. You can find a list by using

>>> help(Tk.bind)

and can find further information by consulting the sources described earlier.

The Final Program 

At this point, we have roughly what we need to write the program. We just need to figure out the names of the widgets used for small text fields and larger text areas. A quick look at the documentation tells us that Entry is what we want for the single-line text fields. A multi-line, scrolled text area can be constructed by combining Text and Scrollbar, but there’s already an implementation available in the tkinter. scrolledtext module. The contents of an Entry can be extracted using its get method, while for the ScrolledText object, we will use the delete and insert methods, with appropriate arguments to indicate locations in the text. In our case, we’ll use '1.0' to specify the first line and the zeroth character (i.e., before the first character), END for the end of the text, and INSERT for the current insertion point. The resulting program is shown in Listing 12-1 and Figure 12-2.
Listing 12-1. Simple GUI Text Editor

from tkinter import * 

from tkinter.scrolledtext import ScrolledText

def load():

    with open(filename.get()) as file:

        contents.delete('1.0', END)

        contents.insert(INSERT, file.read())

def save():

    with open(filename.get(), 'w') as file:

        file.write(contents.get('1.0', END))

top = Tk() 

top.title("Simple Editor")

contents = ScrolledText() 

contents.pack(side=BOTTOM, expand=True, fill=BOTH)

filename = Entry() 

filename.pack(side=LEFT, expand=True, fill=X)

Button(text='Open', command=load).pack(side=LEFT) 

Button(text='Save', command=save).pack(side=LEFT)

mainloop()

You can try the editor using the following steps:
 1. Run the program. You should get a window like the one in the previous runs.
 2. Type something in the large text area (for example, Hello, world!).
 3. Type a file name in the small text field (for example, hello.txt). Make sure that this file does not already exist or it will be overwritten.
 4. Click the Save button.
 5. Quit the program.
 6. Restart the program.
 7. Type the same file name in the little text field.
 8. Click the Open button. The text of the file should reappear in the large text area.
 9. Edit the file to your heart’s content, and save it again.

Now you can keep opening, editing, and saving until you grow tired of that. Then you can start thinking of improvements. How about allowing your program to download files with the urllib module, for example?
    You might also consider using more object-oriented design in your programs, of course. For example, you may want to manage the main application as an instance of a custom application class with methods for setting up the various widgets and bindings. See Chapter 28 for some examples. And as with any GUI package, Tkinter has a great selection of widgets and other classes for you to use. You should use help(tkinter) or consult the documentation for information on any graphical element you would like to use.

Using Something Else 

The basics of most GUI toolkits are roughly the same. Unfortunately, however, when learning how to use a new package, it takes time to find your way through all the details that enable you to do exactly what you want. So you should take your time before deciding which package you want to work with (see, for example, the section on other GUI packages in the Python standard library reference) and then immerse yourself in its documentation and start writing code. I hope this chapter has provided the basic concepts you need to make sense of that documentation.

Comments

Popular posts from this blog

Strings | Core Python 3.8

Strings  Now what was all that "Hello, " + name + "!" stuff about? The first program in this chapter was simply print("Hello, world!") It is customary to begin with a program like this in programming tutorials. The problem is that I haven’t really explained how it works yet. You know the basics of the print statement (I’ll have more to say about that later), but what is "Hello, world!"? It’s called a string (as in “a string of characters”). Strings are found in almost every useful, real-world Python program and have many uses. Their main use is to represent bits of text, such as the exclamation “Hello, world!” Single-Quoted Strings and Escaping Quotes Strings are values, just as numbers are: >>> "Hello, world!"  'Hello, world!' There is one thing that may be a bit surprising about this example, though: when Python printed out our string, it used single quotes, whereas we used double quotes. What’s the differ...

Variables and Statements | Core Python 3.8

Variables  Another concept that might be familiar to you is variables. If algebra is but a distant memory, don’t worry: variables in Python are easy to understand. A variable is a name that represents (or refers to) some value. For example, you might want the name x to represent 3. To make it so, simply execute the following: >>> x = 3 This is called an assignment. We assign the value 3 to the variable x. Another way of putting this is to say that we bind the variable x to the value (or object) 3. After you’ve assigned a value to a variable, you can use the variable in expressions. >>> x * 2  6 Unlike some other languages, you can’t use a variable before you bind it to something. There is no “default value.” ■ Note  the simple story is that names, or identifiers, in python consist of letters, digits, and underscore characters (_). they can’t begin with a digit, so Plan9 is a valid variable name, whereas Plan is not. Statements  Until...

Files and Stuff | Core Python 3.8

So far, we’ve mainly been working with data structures that reside in the interpreter itself. What little interaction our programs have had with the outside world has been through input and print. In this chapter, we go one step further and let our programs catch a glimpse of a larger world: the world of files and streams. The functions and objects described in this chapter will enable you to store data between program invocations and to process data from other programs. Opening Files  You can open files with the open function, which lives in the io module but is automatically imported for you. It takes a file name as its only mandatory argument and returns a file object. Assuming that you have a text file (created with your text editor, perhaps) called somefile.txt stored in the current directory, you can open it like this: >>> f = open('somefile.txt') You can also specify the full path to the file, if it’s located somewhere else. If it doesn’t exist, however, ...

Unicode, bytes, and bytearray | Core Python 3.8

Python strings represent text using a scheme known as Unicode. The way this works for most basic programs is pretty transparent, so if you’d like, you could skip this section for now and read up on the topic as needed. However, as string and text file handling is one of the main uses of Python code, it probably wouldn’t hurt to at least skim this section.     Abstractly, each Unicode character is represented by a so-called code point, which is simply its number in the Unicode standard. This allows you to refer to more than 120,000 characters in 129 writing systems in a way that should be recognizable by any modern software. Of course, your keyboard won’t have hundreds of thousands of keys, so there are general mechanisms for specifying Unicode characters, either by 16- or 32-bit hexadecimal literals (prefixing them with \u or \U, respectively) or by their Unicode name (using \N{name}). >>> "\u00C6"  'Æ'  >>> "\U0001F60A"  '...

Lists and Tuples | Core Python 3.8

This chapter introduces a new concept: data structures. A data structure is a collection of data elements (such as numbers or characters, or even other data structures) that is structured in some way, such as by numbering the elements. The most basic data structure in Python is the sequence. Each element of a sequence is assigned a number—its position, or index. The first index is zero, the second index is one, and so forth. Some programming languages number their sequence elements starting with one, but the zeroindexing convention has a natural interpretation of an offset from the beginning of the sequence, with negative indexes wrapping around to the end. If you find the numbering a bit odd, I can assure you that you’ll most likely get used to it pretty fast.     This chapter begins with an overview of sequences and then covers some operations that are common to all sequences, including lists and tuples. These operations will also work with strings, which will be use...