PySimpleGUI: The name says it all - it is a simple way to make simple GUIs - not appropriate for anything that requires fancy and accurate layout or hyper-responsiveness. PySimpleGUI is mostly the work of one programmer and is a mostly consistent GUI wrapper around Tkinter, Remi, Wx and Qt (although, only the Tkinter one is complete). Its basic layout is a column of rows - described as a list of list of Elements; although you can nest these layouts using frame & column elements.
The primary reference is PySimpleGUI @ readthedocs, especially the Cookbook full of examples
And of course, see our slides!
PySimpleGUI can run (as a tkinter app) on Android. pysimplegui.readthedocs.io/en/latest/#android-version
Here are some snippets that were needed by some students and you might want to include in larger projects
A simple clicker app to demonstrate how to use the time module and also the timeout option to Window.read
import PySimpleGUI as sg
import time
start_time = time.time()
time_taken = 0 # time taken in s
progress = 0
layout = [
[sg.B("Add"), sg.B("Subtract"),
sg.T(f"time = {time_taken:.1f}", key="time", size=(20,1))],
[sg.ProgressBar(100, key="pb", size=(30, 20)),
sg.T(f"{progress}%", key="jam", size=(5,1))]]
window = sg.Window('Progress and Timer', layout, finalize=True)
def update():
window["pb"].update_bar(progress)
window["jam"](f"{progress}%")
window["time"](f"time taken = {time_taken:.1f}")
def end_screen(message='', title=''):
update()
sg.popup(message, title=title, auto_close=True, auto_close_duration=5)
while True:
update()
event, values = window.read(timeout=100)
if event is None: break
if event == "Add":
progress += 1
if progress == 100:
end_screen("You did it!", title="yay!"); break
elif event == "Subtract":
if progress > 0:
progress -= 1
time_taken = time.time() - start_time
if time_taken > 20:
end_screen("Too slow", title="boo!"); break
window.close()
This is my preferred option for a multiple layouts in an app - although, it is not the solution recommended in the docs.
import PySimpleGUI as sg
first_layout = [
[sg.Text("Press 'Next' to start the quiz!")],
[sg.Text("", size=(30, 1))],
[sg.Button("Start", key="start")]
]
second_layout = [
[sg.Text("Amazing!", size=(30, 1))],
[sg.Text("You Win...")],
[sg.Button("Claim Prize", key="claim")]
]
layout = [
[sg.Column(first_layout, key="page1")],
[sg.Column(second_layout, key="page2")]
]
window = sg.Window("Test app", layout, finalize=True)
window["page2"].hide_row()
while True:
event, values = window.read()
print(event, values)
if event is None:
break
elif event == "start":
window["page2"].unhide_row()
window["page1"].hide_row()
elif event == "claim":
window["page1"].unhide_row()
window["page2"].hide_row()
window.close()
import PySimpleGUI as sg
from datetime import time, date, datetime, timedelta
now = datetime.now().time()
# alarm = time.fromisoformat("16:00:10")
alarm = (datetime.now() + timedelta(seconds=30)).time() # for testing!
def time_diff(time1, time2):
date1 = datetime.combine(date.today(), time1)
date2 = datetime.combine(date.today(), time2)
return date2 - date1
layout = [
[sg.Text(f"Current Time = {time.strftime(now, '%H:%M:%S')}",
key="now", size=(20,1))],
[sg.Text(f" Alarm Time = {time.strftime(alarm, '%H:%M:%S')}",
key="alarm", size=(20,1))],
[sg.Text(f"Second left {0}", key="left", size=(20,1))],
[],
[sg.Button("Set New Alarm", key="set", size=(20, 1))]
]
window = sg.Window('Alarm Test', layout)
window.finalize()
def update():
global now, diff
now = datetime.now().time()
diff = time_diff(alarm, now)
window["now"](f"Current Time = {time.strftime(now, '%H:%M:%S')}")
window["alarm"](f" Alarm Time = {time.strftime(alarm, '%H:%M:%S')}")
window["left"](f"Second left {round(-diff.total_seconds())}")
while True:
event, values = window.read(timeout=1000, timeout_key="timeout")
if event is None: break
elif event == "timeout":
update()
if abs(diff.total_seconds()) < 1: sg.popup("ALARM")
elif event == "set":
alarm_str = sg.popup_get_text("Enter the time in the 24-hour format HH:MM:SS", title="New alarm", keep_on_top=True)
try:
alarm = time.fromisoformat(alarm_str)
except ValueError: # incorrect format
pass
except TypeError: # pressed cancel, so alarm_str is None
pass
window.close()
This is built from the Calendar Popup that comes with PySimpleGUI. It is not a perfectly abstracted widget, but will do - it hides all required state, functionality (and of course, code) in the Calendar class
"""Modified from popup_get_date from PySimpleGUI.py"""
import calendar
import datetime
import itertools
import PySimpleGUI as sg
class Calendar:
pass # details in this file: PySG-Calendar-Class.py
# Create the Calendar object and its layout
cal = Calendar()
cal_layout = cal.make_cal_layout()
left_layout = [
[sg.Text("Full name", size=(14, 1))],
[sg.Input(size=(25, 1), key="name")],
[sg.Text()],
[sg.Text("Selected date", size=(14, 1))],
[sg.Text("", size=(25, 1), key="date")],
]
layout = [
[sg.Text("This is an example app using a Calendar class!")],
[sg.Frame("", left_layout), sg.Frame("", cal_layout)],
]
window = sg.Window("Example app with calendar", layout, finalize=True)
# Initialize Calendar now that the window is made
cal.init_cal(window)
while True: # Event Loop
event, values = window.read()
if event in (None,): break
else: # Handel other events - including calendar ones!
cal.handle(window, event, values)
window["date"](cal.chosen_year_mon_day)
window.close()