24. Graphical user interfaces
[status: somewhat fleshed out, but not complete]
Prerequisites
The 10-hour “serious programming” course.
A GNU/Linux system with python3 and python3-tk installed (on an Ubuntu 16.04 system this can be done with
sudo apt install python3-tk
)
24.1. A chat about sources of input in a GUI
The programs we first write with text-based user interfaces, like the tic-tac-toe program in the basic course, have a very simple flow:
You get input from the keyboard (and only from the keyboard).
You do some processing based on that input.
You write some output to the terminal.
Later, in Section 2, we learned to write programs which read data from a file. Still, even in this kind of program you are always only reading input from one place at a time.
At this point I talk to the class about how there are two types of programs that do things quite differently: network programs and GUI programs.
In network programs you can have connections open to several different hosts, and you might be reading input from several of them at once.
In A GUI program you can have input from the keyboard, you can also have mouse movement and mouse clicks. The program also has to track other events that can affect its behavior, like the movement of a window so that a different portion of it is exposed.
GUI programs often use what’s called an *event loop*: you set up your programs layout with windows and widgets and then go in to an infinite loop where you call a function that checks if any events (mouse, keyboard, …) have occurred.
In discussing this I would then to go the whiteboard and draw an example of a simple GUI with a couple of buttons. With that up I would discuss what happens when the user clicks on something?
This leads to the discussion of a *callback* or *callbacks function*.
24.2. Widgets and widget sets
Discuss what are widgets? Little self-contained user interface bits (like buttons and text entry fields and canvases) which you can place in your program’s graphical interface.
As is often the case in the free software world, there is a dizzying array of different *widget sets* available for programming in Python. Tkinter, wxPython, PyQt, PyGTK, PySimpleGui, Kivy, …
The most basic widget set, Tkinter, is included as a standard part of Python. It allows you to develop reasonable comprehensive graphical interfaces, so I will use that as our starting point.
Another which has gained adoption since 2018 is PySimpleGUI, and we will give it a quick glance at the end of this chapter.
24.3. The simplest programs
24.3.1. The programs
Simple single OK button program.
#! /usr/bin/env python3
import tkinter as tk
root = tk.Tk()
okButton = tk.Button(root, text='OK')
okButton.pack()
root.mainloop()
Sure, but that does nothing. Let’s make it do something when you press the button:
#! /usr/bin/env python3
import tkinter as tk
def printOK():
print('OK!!')
def main():
root = tk.Tk()
okButton = tk.Button(root, text='OK', command=printOK)
okButton.pack()
root.mainloop()
main()
Now let’s make it able to quit with a “Quit” button:
#! /usr/bin/env python3
import tkinter as tk
def printOK():
print('OK!!')
def main():
root = tk.Tk()
okButton = tk.Button(root, text='OK', command=printOK)
okButton.pack()
quitButton = tk.Button(root, text='Quit', command=root.destroy)
quitButton.pack()
root.mainloop()
main()
Observing this simple program raises a couple of questions about things we saw in it:
24.3.3. A tour of widgets
We only saw button widgets, but this is a chance to point out what other widgets there are. It’s hard to get an exhaustive list since one can write custom widgets, but here are some to mention:
button
canvas
checkbutton
combobox
entry
frame
label
labelframe
listbox
menu
menubutton
message
notebook
tk_optionMenu
panedwindow
progressbar
radiobutton
scale
scrollbar
separator
sizegrip
spinbox
text
treeview
24.4. Following a tutorial
We will now follow this tutorial:
https://www.tutorialspoint.com/python3/python_gui_programming.htm
This tutorial has links to most of the basic Tkinter widgets, with examples of how to use each one. In the course I have the students bring them up one by one, pasting them in to the python3 interpreter to see them at work.
The one that might be most interesting is the Scale widget: it shows three interacting widgets where the slider sets a value, the button then reads that value and causes a label to be updated. I would go through that example in greater detail.
24.5. Cellular automata on a canvas
Students might want to read this before going through this chapter:
https://en.wikipedia.org/wiki/Elementary_cellular_automaton
24.5.1. A simply drawing of the CA
We have an example of a cellular automaton elsewhere in the book (Section 28). We can use the routines in that program to compute the cellular automaton, and just add the graphical portion in this program.
Download the simple_ca.py program from
Section 28.3 and save it in a file called
simple_ca.py
.
Then take a look at the draw_ca.py
program in
Listing 24.5.1 and try running it.
#! /usr/bin/env python3
"""draw a cellular automaton"""
import time
import math
import sys
sys.path.append('../emergent-behavior')
from simple_ca import *
## we use the tkinter widget set; this seems to come automatically
## with python3 on ubuntu 16.04, but on some systems one might need to
## install a package with a name like python3-tk
from tkinter import *
def main():
## how many steps and cells for our CA
n_steps = 200
n_cells = 200
## we will make each cell be 4x4 pixels
canvas_width = 4*n_cells
canvas_height = 4*n_steps
## prepare a basic canvas
root = Tk()
ca_canvas = Canvas(root,
width=canvas_width,
height=canvas_height)
ca_canvas.pack() # boiler-plate: we always call pack() on tk windows
# row = set_first_row_random(n_cells)
# row = set_first_row_specific_points(n_cells, [40])
row = set_first_row_specific_points(n_cells, [12, 40, 51, 52, 60, 110, 111,
160, 161, 162, 163, 164, 165,
166, 167, 168, 169, 170, 171, 177])
# row = set_first_row_specific_points(n_cells, list(range(int(n_cells/2), n_cells)))
# row = set_first_row_specific_points(n_cells, [12, 13, 50, 51, 70, 71, 99, 100])
## now set the rule
rule = '01101000' # the basic rule
# rule = '00011110' # the famous rule 30
# rule = '01101110' # the famous rule 110
draw_row(ca_canvas, 0, row)
for i in range(1, n_steps):
row = take_step(rule, row)
draw_row(ca_canvas, i, row)
mainloop()
def draw_row(w, pos, row):
color_map = ['black', 'white', 'red', 'green', 'blue', 'yellow']
for i, cell in enumerate(row):
color = color_map[cell % len(color_map)]
w.create_rectangle(4*i, 4*pos, 4*i+4, 4*pos+4, fill=color)
w.update()
## update the canvas
# if i % 10 == 0:
# w.update()
w.update()
if __name__ == '__main__':
main()
Change the initial conditions, the size of the automaton, experiment with it.
Modify draw_ca.py to have a first row editor: a small canvas which allows you to modify the cells in the first row so that you can run any configuration you choose.
Modify draw_ca.py to have a rule editor: a small canvas which allows you to modify the rule for the cellular automaton evolution. You should also have widgets to allow a different number of cells, a different number of rows, and more or less states or neighbors.
You might have noticed that the drawing of the canvas gets slower
and slower as you have more and more squares. You can get around
this by commenting out the w.update()
call so that the cellular
automaton only gets drawn at the end of the run, but then you miss
out on seeing the progression of the cellular automaton. Modify
draw_ca.py
to be more efficient in drawing the canvas. You can
use the information at this stack overflow link as a guide:
24.5.2. Adding controls to the program
Then save draw_ca_with_controls.py
and study it and run
it. At this time the program is not yet too clean (FIXME: update this
text when I update the program), but we can discuss the new mechanisms
that appear in this program:
The canvas does not just run automatically: we control it with the buttons.
We use the canvas’s
after()
function to make the canvas draw “in the background” while the controls are still active. This means we can pause, for example.
Introduce a “first row editor” widget between the controls and the canvas. This recognizes mouse clicks and lets you set the initial cell values so you don’t have to set them with code in the program.
introduce a “rule editor” widget, possibly on the same row as the control widgets. You could start by just taking a number between 0 and 255. Them move on to 8 squares or checkboxes, where you would click on them to activate the binary cells that end up in the rule string. But the coolest might be to make a widget that shows the 3 cells and their child, with the 8 possible 3-cell configurations, and picking if they turn in to 1 or 0.
24.6. Conway’s game of life
Goal: create a canvas which allows you to click to select initial cell values. Then kick off Conway’s game of life rule.
I don’t have much explanatory text yet, but for now let’s discuss the program we have.
Download the conway_life.py
program from
Section 28.3 and save it in a file called
conway_life.py
.
Then save draw_conway_life.py
.
We study the programs together and see how the GUI version works, and then run it.
24.8. A glance at PySimpleGUI
Start with:
$ pip3 install PySimpleGUI
Following the tutorial at https://realpython.com/pysimplegui-python/
(archived at
https://web.archive.org/web/20231225142347/https://realpython.com/pysimplegui-python/
) let us create ok_psg.py
:
#! /usr/bin/env python3
import PySimpleGUI as sg
# layout = [[sg.Text("Hello from PySimpleGUI")], [sg.Button("OK")]]
layout = [[sg.Button("OK")]]
# Create the window
window = sg.Window("OK button", layout)
# Create an event loop
while True:
event, values = window.read()
# End program if user closes window
if event == sg.WIN_CLOSED:
break
window.close()
24.9. Other resources
https://tkdocs.com/tutorial/ (does many languages side-by-side)
http://zetcode.com/gui/tkinter/ (object oriented; this is too soon to use OOP in this course)
https://likegeeks.com/python-gui-examples-tkinter-tutorial/ (maybe good)
https://dzone.com/articles/python-gui-examples-tkinter-tutorial-like-geeks (maybe good)
https://www.python-course.eu/tkinter_labels.php (maybe good because it has modern preferences like “import tkinter as tk”, but maybe too lengthy in the early examples)
https://www.tutorialspoint.com/python3/python_gui_programming.htm