Book Image

Tkinter GUI Programming by Example

Book Image

Tkinter GUI Programming by Example

Overview of this book

Tkinter is a modular, cross-platform application development toolkit for Python. When developing GUI-rich applications, the most important choices are which programming language(s) and which GUI framework to use. Python and Tkinter prove to be a great combination. This book will get you familiar with Tkinter by having you create fun and interactive projects. These projects have varying degrees of complexity. We'll start with a simple project, where you'll learn the fundamentals of GUI programming and the basics of working with a Tkinter application. After getting the basics right, we'll move on to creating a project of slightly increased complexity, such as a highly customizable Python editor. In the next project, we'll crank up the complexity level to create an instant messaging app. Toward the end, we'll discuss various ways of packaging our applications so that they can be shared and installed on other machines without the user having to learn how to install and run Python programs.
Table of Contents (18 chapters)
Title Page
Copyright and Credits
Packt Upsell
Contributors
Preface
Index

How will the code be structured?


Tkinter exposes many classes. These are known as widgets. A widget is typically any part of the application that needs to be drawn onto the screen, including the main window.

A Tkinter application always needs to have a main window. This is what will be drawn on the screen for the user to see. This is crucial for any GUI application, so much so that if you do not define one, Tkinter will try to create one for you (though you should never rely on this!). The widget that performs this job is called Tk.

The Tk widget exposes various window properties, such as the text within the top bar of the application, the size of the application, its position on screen, whether it can be resized, and even the icon which appears in the top right-hand corner (on Windows only).

Because of this feature exposure, it is very common for the main class of an application to inherit from the Tk widget, though any Tkinter widget can be subclassed to add program-specific functionality.

There is no set convention for what the subclass should be called. Some like to call it Root, some choose App, and others (such as myself) prefer to name it after the program itself. For example, a shopping list program would have a class called ShoppingList that inherits from Tk. Bear this in mind when looking through other sources of information on Tkinter.

Once you have a main window defined, you can begin adding other widgets into it. All other widgets must belong to a parent which has the ability to display them, such as a Tk or Frame. Each widget is only visible if its parent is. This allows us to group widgets into different screens and show or hide groups of them as need be.

Widgets are placed into their parents using special functions called geometry managers. There are three geometry managers available in Tkinter – pack, grid, and place. Let's take a look at each of them in detail.

Geometry managers

Geometry managers serve the purpose of deciding where in the parent widget to render its children. Each of the three geometry managers uses a different strategy and therefore takes different arguments. Let's go over each one in detail, looking at how it decides the positions of new widgets and what sort of arguments need to be provided.

pack

The pack geometry manager acts based on the concept of using up free space within the parent widget. When packing, you can specify at which end of the free space to put the widget, and how it will grow along with said free space (as the window itself grows and shrinks). The geometry manager than assigns widgets into said free space, leaving as little empty space as possible.

The pack geometry manager is primarily controlled by three keyword arguments:

  • side: On which end of the available space do you want to place the widget? The options are defined as constants within Tkinter, as LEFT, RIGHT, TOP, and BOTTOM.
  • fill: Do you want the widget to fill any available space around it? The options are also constants: X or Y. These are Cartesian, meaning X is horizontal and Y is vertical. If you want the widget to expand in both directions, use the BOTH constant.
  • expand: Should the widget resize when the window does? This argument is a Boolean, so you can pass True or 1 to make the widget grow with the window.

These are not the only arguments that can be provided to pack; there are others which handle things such as spacing, but these are the main ones you will use. The packgeometry manager is somewhat difficult to explain, but tends to create very readable code thanks to its use of words to describe positions.

The order in which widgets are packed matters greatly. Suppose you have two buttons which you wish to stack vertically, with one underneath the other. The first button, which you call pack(side=tk.BOTTOM) on, will be at the very bottom of the main window. The next widget, which is packed with side=tk.BOTTOM, will then appear above it. Bear this in mind if your widgets appear to be out of order when using pack as your geometry manager.

grid

The grid—as the name suggests—treats the parent widget as a grid containing rows and columns of cells. If you are familiar with spreadsheet software, grid will work in the same way. The grid lines will not be visible, they are just conceptual.

To specify the position within the grid, the row and column keywords are used. These accept integer values and begin at 0, not 1. A widget placed with grid(row=0, column=0) will be to the left of a widget at grid(row=0, column=1). Underneath these would sit a widget placed at grid(row=1, column=0).

To make a widget span more than one cell, use columnspan for a horizontal size increase and rowspan for a vertical increase. So, to make our hypothetical bottom widget sit below both, the full argument set would be grid(row=1, column=0, columnspan=2).

By default, a widget will sit in the center of its assigned cell(s). In order to make the widget touch the very edge of its cell, we can use the sticky argument. This argument takes any number of four constants: N, S, E, and W. These are abbreviations for North, South, East, and West. Passing in W or E will align the widget to the left or right, respectively. S and N will align to the bottom and top.

These constants can be combined as desired, so NE will align top right and SW will sit the widget bottom left.

If you wish for the widget to span the entire vertical space, use NS. Similarly, use EW to stretch to the full size in the horizontal direction.

If you instead want the widget to fill the whole cell edge to edge, NSEW will let you do this.

Note

The pack and grid are both intended to lay out the entire content of a parent widget and apply different logic to decide where each new widget added should go. For this reason, they cannot be combined inside the same parent. Once one widget is inserted using pack or grid, all other widgets must use the same geometry manager. You can, however, pack widgets into one Frame, grid widgets into another, then pack/grid both of those Frame widgets into the same parent. 

place

Unlike pack and grid, which automatically calculate where each new widget is added, place can be used in order to specify an exact location for a particular widget. place takes either x and y coordinates (in pixels) to specify an exact spot, which will not change as the window is resized, or relative arguments to its parent, allowing the widget to move with the size of the window.

To place a widget at (5, 10) within the window, you would write widget.place(x=5, y=10).

To keep a widget in the direct center, you would use widget.place(relx=0.5, rely=0.5).

place also takes sizing options, so to keep a widget at 50 percent width and 25 percent height of the window, add (relwidth=0.5, relheight=0.25).

place is rarely used in bigger applications due to its lack of flexibility. It can be tiresome keeping track of exact coordinates for a widget, and as things change with the application, widgets may resize, causing unintended overlapping.

For a smaller window with only one or two widgets – say a custom pop-up message – place could be a viable choice of geometry manager, since it allows for very easy centering of said widgets.

One thing to note is that place can be used alongside pack or grid within the same parent widget. This means that if you have just one widget which you need to put in a certain location, you can do so quickly without having to restructure your already packed or gridded widgets.

To pack or to grid?

Using pack versus grid in your application is mostly down to personal preference. There doesn't seem to be a particularly dominant reason to use one over the other.

The main advantage of pack is the code tends to be very readable. pack uses words such as left and top to make it clear where the programmer wants the widget to go.

When using pack, sections of the window are also split using frames to allow for much greater control. When variables are named sensibly, this allows anyone changing the code to know exactly which part of a window the widget will end up in (by its parent Frame) and prevents them from having unexpected consequences when changing widgets, such as resizing a widget in the top-left corner of an application, knocking a widget at the bottom out of alignment.

Note

The grid can also take advantage of Frame widgets too, but this can sometimes cause alignment issues.

Finally, pack works out widget positions based mainly on the argument and the order in which they are added. This means that when a new widget is added among existing ones, it is usually quite easy to get it into the correct spot. Simply adjust the order in which your widget.pack() calls occur. When using grid, you may need to change quite a few row and column arguments in order to slot the widget where you need it and keep everything else in their correct positions.

The great advantage of grid is its code simplicity to layout complexity ratio. Without the need to split your application into frames, you can save many lines of code and lay out a complicated window design with essentially just one line of code per widget.

You also don't need to worry about the order in which you add your widgets to their parent as the numerical grid system will apply regardless.

In the end, both prove to be good tools for the job and there is no need to use one if you prefer the other.

My personal preference is to use pack for main windows which may change quite a bit during development, and sometimes grid for smaller windows or layouts which are written in one go. Any additional windows for an application which would require more than two Frame widget are often better off being managed by grid for simplicity's sake.

Examples in this book will cover both grid and pack, so you will be able to practice both and decide which you prefer.