Graphical User Interfaces

Object oriented programming and particularly inheritance is commonly used for creating GUIs. There are a large number of different frameworks supporting building GUIs. The following are particularly relevant:

  • TkInter - This is the official/default GUI framework
  • guidata - A GUI framework for dataset display and editing
  • VTK - A GUI framework for data visualization
  • pyqtgraph - A GUI framework for data visualization, easily installed with conda install pyqtgraph
  • matplotlib - As well as creating plots matplotlib can support interaction

TkInter

TkInter is widely used with plenty of documentation available but may prove somewhat limited for more data intensive applications.

Let's look at a simple example from the documentation


In [37]:
import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

    def createWidgets(self):
        self.hi_there = tk.Button(self)
        self.hi_there["text"] = "Hello World\n(click me)"
        self.hi_there["command"] = self.say_hi
        self.hi_there.pack(side="top")

        self.QUIT = tk.Button(self, text="QUIT", fg="red",
                                            command=root.destroy)
        self.QUIT.pack(side="bottom")

    def say_hi(self):
        print("hi there, everyone!")

root = tk.Tk()
app = Application(master=root)
app.mainloop()

Although this works, it is visually unappealing. We can improve on this using styles and themes.


In [48]:
import tkinter as tk
from tkinter import ttk


class Application(ttk.Frame):
    def __init__(self, master=None):
        super().__init__(master, padding="3 3 12 12")
        self.grid(column=0, row=0, )
        self.createWidgets()
        self.master.title('Test')

    def createWidgets(self):
        self.hi_there = ttk.Button(self)
        self.hi_there["text"] = "Hello World\n(click me)"
        self.hi_there["command"] = self.say_hi

        self.QUIT = ttk.Button(self, text="QUIT", style='Alert.TButton', command=root.destroy)

        for child in self.winfo_children(): 
            child.grid_configure(padx=10, pady=10)

    def say_hi(self):
        print("hi there, everyone!")

        

root = tk.Tk()
app = Application(master=root)
s = ttk.Style()
s.configure('TButton', font='helvetica 24')
s.configure('Alert.TButton', foreground='red')
root.mainloop()

As our applications get more complicated we must give greater thought to the layout. The following example comes from the TkDocs site.


In [28]:
from tkinter import *
from tkinter import ttk

def calculate(*args):
    try:
        value = float(feet.get())
        meters.set((0.3048 * value * 10000.0 + 0.5)/10000.0)
    except ValueError:
        pass
    
root = Tk()
root.title("Feet to Meters")

mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)

feet = StringVar()
meters = StringVar()

feet_entry = ttk.Entry(mainframe, width=7, textvariable=feet)
feet_entry.grid(column=2, row=1, sticky=(W, E))

ttk.Label(mainframe, textvariable=meters).grid(column=2, row=2, sticky=(W, E))
ttk.Button(mainframe, text="Calculate", command=calculate).grid(column=3, row=3, sticky=W)

ttk.Label(mainframe, text="feet").grid(column=3, row=1, sticky=W)
ttk.Label(mainframe, text="is equivalent to").grid(column=1, row=2, sticky=E)
ttk.Label(mainframe, text="meters").grid(column=3, row=2, sticky=W)

for child in mainframe.winfo_children(): child.grid_configure(padx=5, pady=5)

feet_entry.focus()
root.bind('<Return>', calculate)

root.mainloop()

Matplotlib

For simple programs, displaying data and taking basic input, often a command line application will be much faster to implement than a GUI. The times when I have moved away from the command line it has been to interact with image data and plots. Here, matplotlib often works very well. Either it can be embedded in a larger application or it can be used directly.

There are a number of examples on the matplotlib site.

Here is one stripped down example of one recent GUI I have used.


In [ ]:
"""
Do a mouseclick somewhere, move the mouse to some destination, release
the button.  This class gives click- and release-events and also draws
a line or a box from the click-point to the actual mouseposition
(within the same axes) until the button is released.  Within the
method 'self.ignore()' it is checked wether the button from eventpress
and eventrelease are the same.

"""
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook


def line_select_callback(eclick, erelease):
    'eclick and erelease are the press and release events'
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata
    print ("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))
    print (" The button you used were: %s %s" % (eclick.button, erelease.button))

    
def toggle_selector(event):
    print (' Key pressed.')
    if event.key in ['Q', 'q'] and toggle_selector.RS.active:
        print (' RectangleSelector deactivated.')
        toggle_selector.RS.set_active(False)
    if event.key in ['A', 'a'] and not toggle_selector.RS.active:
        print (' RectangleSelector activated.')
        toggle_selector.RS.set_active(True)


        
image_file = cbook.get_sample_data('grace_hopper.png')
image = plt.imread(image_file)
fig, current_ax = plt.subplots()
plt.imshow(image)
toggle_selector.RS = RectangleSelector(current_ax, 
        line_select_callback,
        drawtype='box', useblit=True,
        button=[1,3], # don't use middle button
        minspanx=5, minspany=5,
        spancoords='pixels')
plt.connect('key_press_event', toggle_selector)
plt.show()

In [ ]: