Week 12 Day 1: Requested topic: GUIs
Objectives:
- Go over quick overview of available GUI libraries
This is bonus material unless we finish the presentations early; in which case we'll look at it in class. I may return to this if there is still interest for the student requested topics day.
Today, some of the code cannot be run on the OSC or in a notebook. Any GUI that does not produce web content will need to be run by itself.
GUIs
There are a ton of options. Here is a little sampling:
- TKinter: Old, built into Python. Not pythonic, limited in scope, ugly, and a bit slow. Better with a wrapper.
- PySimpleGUI: Designed to be both simple and powerful. Very new, but very promising for simple to mid-level GUIs. A new PySimpleGUI_Qt front end
- Qt: Industrial grade GUI toolkit for C++. Has two Python bindings.
- PyQT: Powerful, GPL only bindings. First to support Qt5 and Python 3.
- PySide: Acquired by Qt, to become the "official" bindings. Old, still Qt4.
- Qt for Python (PySide 2): Soon to become the first "official" release of PySide under Qt
- wxPython: A framework built for Python.
- PyGame: A OpenGL gui made for games. Waiting on a big 2.0 release.
- kivy: Some fancy cross platform thing, but not easy to install/integrate as far as I can tell.
See Top 7 Python GUI frameworks for more.
For Jupyter, you can also use widgets to make a make-shift GUI that might be good enough. See the interact widgets and other widgets.
from bokeh.io import output_notebook, show, push_notebook
from bokeh.plotting import figure
output_notebook()
from ipywidgets import interact
import numpy as np
t = np.linspace(-1.25*np.pi, 1.25*np.pi, 2000)
y = np.zeros_like(t)
for n in range(1,100,2):
y += 1/n * np.sin(n * t)
y *= 4/np.pi
p = figure(width=500, height=300)
r = p.line(t, y)
show(p, notebook_handle=True);
@interact
def funct(its=(1,50)):
t = np.linspace(-1.25*np.pi, 1.25*np.pi, 2000)
y = np.zeros_like(t)
for i in range(its):
n = 1 + 2*i
y += 1/n * np.sin(n * t)
y *= 4/np.pi
r.data_source.data['y'] = y
push_notebook()
Dialog
This sort of GUI is like a function call. You ask for information, the GUI collects it from the user, and returns to your script. The program flow is still "traditional", that is, it is linear and looks like the code we are used to. The drawback, of course, is the GUI is quite limited in what it can do; it's just a glorified dialog box at best.
Some frameworks like Tkinter provide a few simple one item dialog boxes. Wrappers like PySimpleGUI provide much more powerful multi-part dialog boxes that can do several things at once (though still remain dialog box-like).
Tkinter
Let's look at the standard library module Tkinter. This became quite a bit easier to use in this mode in the last few Python releases, I believe. The tutorial here looks quite good.
# This will not work unless you are local
# or have your x windows display attached to the remote!
from tkinter import messagebox, simpledialog
messagebox.showinfo("Information","Informative message")
messagebox.askyesno("Question","Do you like Python?")
simpledialog.askstring("Input", "What is your first name?")
Notice that ugly dead window that is running? That's the parent window. We could have set it up and controlled it ourselves with application_window = Tk()
. There is also a separate tkinker.filedialog
module that has file choice boxes, and a few other things.
import PySimpleGUI as sg
# Very basic window. Return values as a list
layout = [
[sg.Text('Please enter your Name, Address, Phone')],
[sg.Text('Name', size=(15, 1)), sg.InputText('name')],
[sg.Text('Address', size=(15, 1)), sg.InputText('address')],
[sg.Text('Phone', size=(15, 1)), sg.InputText('phone')],
[sg.Submit(), sg.Cancel()]
]
window = sg.Window('Simple data entry window').Layout(layout)
button, values = window.Read()
window.Close() # Doesn't seem to work for me on Anaconda macOS
# - the window dies instead of closing
print(button, *values)
See lots more PySimpleGUI DemoPrograms here. PySimpleGUI has a Python 2.7 version (called PySimpleGUI27, rather than being the same package), and a very early version of PySimpleGUIQt, which uses a Qt backend instead.
Due to a bug in Anaconda Python on macOS (possibly just my copy?), the kernel must be restarted to remove the window.
Event Loop
This style of GUI is very powerful - you can remain in a single window, and buttons and other parts of the GUI drive the computation. The programming style is different and invasive; instead of a linear flow, you write callbacks that get run when a user performs some action. The overall app has to have something powering it; this is the "event loop".
This is how most GUI frameworks work internally, though wrappers often hide this when they provide the dialog box interfaces. PySimpleGUI seems to do a good job of providing tools and examples for this mode too.
Let's start with tkinter again:
import tkinter as tk
from tkinter import ttk
# Create the application window
window = tk.Tk()
# Create the user interface
my_label = ttk.Label(window, text="Hello World!")
my_label.grid(row=1, column=1)
# Start the GUI event loop
window.mainloop()
For an example of how you could use OO programming to orginize this, try this page.
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
import sys
app = QApplication(sys.argv)
w = QWidget()
b = QLabel(w)
b.setText("Hello World!")
w.setGeometry(100,100,200,50)
b.move(50,20)
w.setWindowTitle("PyQt")
w.show()
app.exec_()
Note: You must restart your kernel. This was not designed to be run from a notebook/long running process.
This is what it's supposed to look like, however:
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
qbtn = QPushButton('Quit', self)
qbtn.clicked.connect(QApplication.instance().quit)
qbtn.resize(qbtn.sizeHint())
qbtn.move(50, 50)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Quit button')
self.show()
# Normally, you wild include: if __name__ == '__main__':
# And sys.exit(app.exec_())
app = QApplication(sys.argv)
ex = Example()
app.exec_()