Graphical User Interface with Python
Python is inherently a programming language ideal for data analysis, and it works well by simply entering commands and seeing the results immediately. For longer programs, or ones that are used frequently, the code is usually written into a file and then executed from the command line or as an element of a more complex set of scripted operations. However many users prefer a graphical user interface or "GUI" where mouse clicks, drop-down menus, check boxes, images, and plots access with the mouse and sometimes the keyboard. Python has that too, through a selection of tools that can be added to your code.
The most developed core component for this purpose is the Tk/Tcl library. In its most recent versions it provides a very direct way of running your programs within a GUI that is attractive, matches the native machine windows in appearance, and is highly responsive. While this is not the only one, it is a solid starting point and may be all you need. Later in this tutorial we will show you how to use Python running in background behind a graphical interface in a browser window which is another technique that is growing in capability as the browsers themselves gain computing power. However, the Tk library will allow you to build free-standing graphical applications that will run on all platforms on which Python runs.
A Tk Tutorial
An excellent starting point is the Tk documentation tutorial which will take you step by step through how to use this library in Python and in other languages. Work through this one first, and then return here and we will illustrated it with a few examples to show you how to use it in Physics and Astronomy applications.
Building a Program
In the tutorial you will see examples of code segments, and a few complete programs. Here, step by step, is how to make one that you can use as a template for something useful. First, you may see lines such as these at the beginning of a Tk program:
from tkinter import * from tkinter import ttk
which add the needed modules to your Python system. When you use "from xxx import *" that takes all the parts of xxx and adds them with their intrinsic names. Usually this is not an issue for such major parts of Python as tk, but it could cause a problem by overwriting or changing the meaning of something you are already using. Also, even if that is not the case, you cannot immediately tell if a function or constant is from tk, ttk or the core Python. An example would be Tk itself. The next lines of a simple program started this way may be
root = Tk() mainframe = ttk.Frame(root, padding=3 3 12 12")
and if we wanted to use a string inside the tkinter program we would have
mystring = StringVar()
somewhere in the code. The function "StringVar()" is not in the core Python. It comes from tkinter. Without the leading "from tkinter import *" the "StringVar()" is not defined.
For long term maintenance, a better approach is to load the module and give it a short name this way:
import tkinter as tk from tkinter import ttk
Now we have to use "tk" along with the component we are taking from tkinter, but the resulting code is much clearer. Let's complete a program that will convert feet to meters, following an example from the Tk website.
def calculate(*args): try: value = float(feet.get()) meters.set((0.3048 * value * 10000.0 + 0.5)/10000.0) except ValueError: pass root = tk.Tk() root.title("Feet to Meters")
mainframe = tk.ttk.Frame(root, padding="3 3 12 12") mainframe.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) mainframe.columnconfigure(0, weight=1) mainframe.rowconfigure(0, weight=1)
feet = tk.StringVar() meters = tk.StringVar()
This way, all the tk-specific terms are clear. Notice the constants too, here "tk.W" for example, come from tkinter. Here's the remainder of the program that wil create a calculator window and run the conversion:
feet_entry = tk.ttk.Entry(mainframe, width=7, textvariable=feet) feet_entry.grid(column=2, row=1, sticky=(tk.W, tk.E)) tk.ttk.Label(mainframe, textvariable=meters).grid(column=2, row=2, sticky=(tk.W, tk.E)) tk.ttk.Button(mainframe, text="Calculate", command=calculate).grid(column=3, row=3, sticky=tk.W) tk.ttk.Label(mainframe, text="feet").grid(column=3, row=1, sticky=tk.W) tk.ttk.Label(mainframe, text="is equivalent to").grid(column=1, row=2, sticky=tk.E) tk.ttk.Label(mainframe, text="meters").grid(column=3, row=2, sticky=tk.W) for child in mainframe.winfo_children(): child.grid_configure(padx=5, pady=5) feet_entry.focus() root.bind('<Return>', calculate)
The result will look like this when it runs:
The elements of the program are
- import tkinter
- define a function "calculate"
- create a graphical environment "root"
- add a frame to the environment
If you start the Python interpreter in a shell and perform the operations step by step up, as soon as the root object is created, a GUI window will appear on the screen. What comes after determines the properties of that display and its contents. For example, adding a title has a obvious effect to this window.
The window contains a frame which is called "mainframe" here, and when that is added the window shrinks to fit the frame. It is quite small because the frame does not contain anything but a few pixels of padding around its border. You can, at this point grab the edges of the tiny box and stretch it on your screen. The command
mainframe.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
creates a grid that is anchored to tk points N, W, E, and S, that is to the corners. Within the grid we add components that will make our interface, and each component has properties we can set too. In this program we will have a frame that will enclose the our interface, and the window will resize automatically to enclose everything.
First, we add the frame:
mainframe.columnconfigure(0, weight=1) mainframe.rowconfigure(0, weight=1)
Then we define the strings that will hold input and the output from the box that will accept the input:
feet = tk.StringVar() feet_entry = tk.ttk.Entry(mainframe, width=7, textvariable=feet)
The object called "feet_entry" represents this box on the screen. The box is located inside the GUI by the grid that is defined in mainframe
feet_entry.grid(column=2, row=1, sticky=(tk.W, tk.E))
and we put it in column 2 and row 1. As soon as this is issued, a box appears in the GUI and is centered because there is nothing more there. The result of the calculation will be a string that we will show on the interface as a label below the input in the second row
meters = tk.StringVar() tk.ttk.Label(mainframe, textvariable=meters).grid(column=2, row=2, sticky=(tk.W, tk.E))
Notice how the string is given as a variable without quotes, so the interface will update when this variable changes. Along with this, we include fixed labels for the input units, an explanation and the output units:
tk.ttk.Label(mainframe, text="feet").grid(column=3, row=1, sticky=tk.W) tk.ttk.Label(mainframe, text="is equivalent to").grid(column=1, row=2, sticky=tk.E) tk.ttk.Label(mainframe, text="meters").grid(column=3, row=2, sticky=tk.W)
Lastly, we add a button that will start the calculation
tk.ttk.Button(mainframe, text="Calculate", command=calculate).grid(column=3, row=3, sticky=tk.W)
All of these elements are children of the frame, and to have them appear on the grid with a little padding we take each one and configure the grid for it
for child in mainframe.winfo_children(): child.grid_configure(padx=5, pady=5)
Now focus the window on the entry box to make it easy on the user and use the Return key to start a conversion (as well as the button)
feet_entry.focus() root.bind('<Return>', calculate)
The GUI is ready to use. Start it with this