Graphical user interfaces

Download all scripts: graphical_user_interfaces.zip

window_w_label.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter

# Inititalize Tkinter by creating a Tk root widget (= a window with a title bar and other decoration provided by your operating system)
root = tkinter.Tk()

# Create a label as a child of the root widget
label = tkinter.Label(root, text = 'Hello, world!')

# Make the label visible and size the root widget to fit the label
label.pack()

# Enter the Tkinter event loop
root.mainloop()

window_w_button.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter

# Inititalize Tkinter by creating a Tk root widget (= a window with a title bar and other decoration provided by your operating system)
root = tkinter.Tk()

# Define a function to be called when the button is clicked
def on_button_click():
	print('...like an arrow')

# Create a button as a child of the root widget
button = tkinter.Button(root, text = 'Time flies...', command = on_button_click)

# Make the button visible and size the root widget to fit the button
button.pack()

# Enter the Tkinter event loop
root.mainloop()

window_w_frame.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter

# Inititalize Tkinter by creating a Tk root widget (= a window with a title bar and other decoration provided by your operating system)
root = tkinter.Tk()

# Define a function to be called when the button is clicked
def on_button_click():
	print('...like an arrow')

# Create a frame as a child of the root widget
frame = tkinter.Frame(root)
frame.pack()

# Create a label as a child of the frame
label = tkinter.Label(frame, text = 'Hello, world!')
label.pack()

# Create a button as a child of the frame
button = tkinter.Button(frame, text = 'Time flies...', command = on_button_click)
button.pack()

# Enter the Tkinter event loop
root.mainloop()

window_w_subclassed_frame.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter

# Define a function to be called when the button is clicked
class SubclassedFrame(tkinter.Frame):

	def __init__(self, parent):  # Special method name __init__ (also called constructor method): this method gets called when an instance of the class is created
		tkinter.Frame.__init__(self) # Call the constructor method of the tkinter.Frame class

		self.label = tkinter.Label(root, text = 'Hello, world!')
		self.label.pack()
		self.button = tkinter.Button(root, text = 'Time flies...', command = self.on_button_click)
		self.button.pack()
	
	def on_button_click(self):
		print('...like an arrow')


# Inititalize Tkinter by creating a Tk root widget (= a window with a title bar and other decoration provided by your operating system)
root = tkinter.Tk()


subclassed_frame = SubclassedFrame(root)

# Enter the Tkinter event loop
root.mainloop()

stop_watch.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter
import time

class StopWatch(tkinter.Frame):  # Define a class 'StopWatch' based on a tkinter.Frame

	def __init__(self, parent):  # Special method name __init__ (also called constructor method): this method gets called when an instance of the class is created
		print('def __init__(self, parent)')
		tkinter.Frame.__init__(self) # Call the constructor method of the tkinter.Frame class
		
		# Create widgets
		self.time_label = tkinter.Label(root, text = '00:00:00')
		self.time_label.pack()
		self.start_stop_button = tkinter.Button(root, text = 'Start', command = self.start_stop)
		self.start_stop_button.pack()
		self.reset_button = tkinter.Button(root, text = 'Reset', command = self.reset)
		self.reset_button.pack()
		
		# Initialize variables
		self.reset()
		
	def reset(self):
		print('def reset(self)')
		self.is_running = False
		self.start_stop_button['text'] = 'Start'
		self.elapsed_time = 0
		self.start_time = None
		self.display_time()
		
	def start_stop(self):
		print('start_stop(self)')
		if self.is_running:  # Stop watch is running -> stop
			self.is_running = False
			self.start_stop_button['text'] = 'Start'
			self.elapsed_time = time.time() - self.start_time
		else:  # Stop watch is not running -> (re-)start
			self.is_running = True
			self.start_stop_button['text'] = 'Stop'
			self.start_time = time.time() - self.elapsed_time
			self.update_time()

	def display_time(self, time = 0):
		print('def display_time(self, time = {})'.format(time))
		m = int(time / 60)  # Minutes
		s = int(time - m * 60)  # Seconds
		cs = int((time - int(time)) * 100)  # Centiseconds (= hundredths of a second)
		self.time_label['text'] = '{:02d}:{:02d}:{:02d}'.format(m, s, cs)
		
	def update_time(self):
		print('def update_time(self)')
		if self.is_running:
			self.display_time(time.time() - self.start_time)
			self.timer = self.after(10, self.update_time)  # Call update_time(self) in 10 microseconds again
		else:
			self.timer = None


# Create a Tk root widget 
root = tkinter.Tk()

# Create a tkinter.Frame as defined by the StopWatch class
stop_watch = StopWatch(root)
stop_watch.pack()

# Enter the Tkinter event loop
root.mainloop()

stop_watch_w_lap_time.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter
import time

class StopWatch(tkinter.Frame):
	
	def __init__(self, root):
		tkinter.Frame.__init__(self)
		
		backgroundcolor = '#eee'
		
		# Style window
		root.title('Stop Watch')
		root.resizable(1, 0) # Vertical, horizontal
		root.configure(bg = backgroundcolor)
		
		# Create widgets
		self.duration = tkinter.Label(
			font = (None, 30, 'bold'),
			bg = backgroundcolor)
		self.duration.pack(
			side = tkinter.TOP)
		
		self.lap_times_list = tkinter.Listbox(
			borderwidth = 0)
		self.lap_times_list.pack(
			side = tkinter.BOTTOM,
			fill = tkinter.BOTH,
			expand = 1)
		for i in range(0, self.lap_times_list.size(), 2):  # Colorize alternating lines of the listbox
			self.lap_times_list.itemconfigure(i, background='#f8f8f8')

		self.start_pause_button = tkinter.Button(
			text = 'Start',
			command = self.start_pause, 
			width = 8,
			default = tkinter.ACTIVE,
			highlightbackground = backgroundcolor)
		self.start_pause_button.pack(
			side = tkinter.LEFT)
		
		self.lap_reset_button = tkinter.Button(
			text = 'Reset',
			command = self.lap_reset,
			width = 8,
			default = tkinter.DISABLED,
			highlightbackground = backgroundcolor)
		self.lap_reset_button.pack(
			side = tkinter.LEFT,
			fill = tkinter.BOTH,
			expand = 1)
		
		root.update()
		# now root.geometry() returns valid size/placement
		root.minsize(root.winfo_width(), root.winfo_height())
		root.maxsize(root.winfo_width(), 0)
				
		# Bind events
		root.bind('<Return>', self.start_pause) # Start / pause stopwatch
		root.bind('<BackSpace>', self.lap_reset) # Reset stopwatch
		root.bind('<space>', self.lap_reset) # Add lap time
		
		# Initialize
		self.start_time = 0
		self.lap_reset()
		
	def lap_reset(self, event = None):
		if not self.start_time: # Stopwatch is not running: reset everything
			self.start_time = 0
			self.stopped_duration = 0
			self.start_pause_button['text'] = 'Start'
			self.lap_reset_button['text'] = 'Lap'
			self.display_duration()
			self.lap_times_list.delete(0, tkinter.END)
		else: # Stopwatch is not running: add lap time to listbox
			self.lap_times_list.insert(tkinter.END, self._mm_ss_cs(self._current_stopped_duration()))
			i = self.lap_times_list.size() - 1
			if i % 2:
				self.lap_times_list.itemconfigure(i, background='#fafafa')
		
	def display_duration(self, seconds = 0):
		self.duration['text'] = self._mm_ss_cs(seconds)
		
	def _current_stopped_duration(self):
		return self.stopped_duration + time.time() - self.start_time
		
	def _mm_ss_cs(self, seconds = 0):
		mm = int( seconds / 60)
		ss = int( seconds - mm * 60)
		cs = int((seconds - mm * 60 - ss) * 100)
		return '{0:02d}:{1:02d}.{2:02d}'.format(mm, ss, cs)
		
	def start_pause(self, event = None):
		if not self.start_time: # Start stopwatch
			self.start_time = time.time()
			self.start_pause_button['text'] = 'Pause'
			self.lap_reset_button['text'] = 'Lap'
		else: # Stop stopwatch
			self.stopped_duration += time.time() - self.start_time
			self.start_time = 0
			self.start_pause_button['text'] = 'Start'
			self.lap_reset_button['text'] = 'Reset'
		self.update()
		
	def update(self):
		if self.start_time:
			self.display_duration(self._current_stopped_duration())
			self.timer = self.after(10, self.update)
		else:
			self.display_duration(self.stopped_duration)
			self.after_cancel(self.timer)
			
if __name__ == '__main__':

	root = tkinter.Tk()
	stop_watch = StopWatch(root)
	root.mainloop()

traffic_light.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter

class TrafficLight(tkinter.Frame):
	
	def __init__(self, root):
		tkinter.Frame.__init__(self)
		
		background_color = '#eee'
		
		# Style window
		root.title('Traffic Light')
		root.resizable(0, 0) # Vertical, horizontal
		# root.configure(bg = background_color)
		
		self.canvas = tkinter.Canvas(
			width = 100, height = 300,
			bg = background_color,
			highlightthickness = 0)  # Get rid of invisible border
		self.canvas.pack() #fill = tkinter.BOTH, expand = 1)
		
		self.lights = {
			'red'   : self.canvas.create_oval(10,  10, 90,  90, width = 0, fill = '#f00', disabledfill = '#efefef', state = tkinter.NORMAL),
			'yellow': self.canvas.create_oval(10, 110, 90, 190, width = 0, fill = '#fc0', disabledfill = '#efefef', state = tkinter.DISABLED),
			'green' : self.canvas.create_oval(10, 210, 90, 290, width = 0, fill = '#393', disabledfill = '#efefef', state = tkinter.DISABLED)}
		
		self.states = [
			{ 'duration' : 5, 'colors' : ('red', )},
			{ 'duration' : 4, 'colors' : ('red', 'yellow')},
			{ 'duration' : 5, 'colors' : ('green', )},
			{ 'duration' : 2/3, 'colors' : ()},
			{ 'duration' : 2/3, 'colors' : ('green', )},
			{ 'duration' : 2/3, 'colors' : ()},
			{ 'duration' : 2/3, 'colors' : ('green', )},
			{ 'duration' : 2/3, 'colors' : ()},
			{ 'duration' : 2/3, 'colors' : ('green', )},
			{ 'duration' : 2/3, 'colors' : ()},
			{ 'duration' : 2/3, 'colors' : ('green', )},
			{ 'duration' : 4, 'colors' : ('yellow', )}
		]
		self.current_state = -1
		self.update()
		
	def update(self):
		
		state = self.states[self.current_state]
		for color in state['colors']:
			self.canvas.itemconfig(self.lights[color], state = tkinter.DISABLED)

		self.current_state  = (self.current_state + 1) % len(self.states)

		state = self.states[self.current_state]
		for color in state['colors']:
			self.canvas.itemconfig(self.lights[color], state = tkinter.NORMAL)
		
		self.timer = self.after(int(state['duration'] * 1000), self.update)
		
if __name__ == '__main__':

	root = tkinter.Tk()
	traffic_light = TrafficLight(root)
	root.mainloop()

"""
See: https://www.wien.gv.at/verkehr/ampeln/images/verkehrs-ampel.gif
"""

analogue_clock.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import math, tkinter, time

# Define a class to crreate a canvas within the Tk root widget and draw an analogue clock
class AnalogueClock(tkinter.Frame):

	def __init__(self, parent, size = 250, minsize = 100, maxsize = 1000):  # Special method name __init__ (also called constructor method): this method gets called when an instance of the class is created
		# Setup the Tk root widget
		self.parent = parent
		self.parent.geometry('{}x{}+100+100'.format(size, size))  # Set the window size and position
		self.parent.minsize(minsize, minsize)  # Set the minimum window size
		self.parent.maxsize(maxsize, maxsize)  # Set the maximum window size
		
		# Add a canvas widget within the Tk root widget
		self.canvas = tkinter.Canvas(root)  # Create a canvas within the Tk root widget
		self.canvas.pack(fill = tkinter.BOTH, expand = 1)  # Pack the canvas to fit the root widget
		self.canvas.bind("<Configure>", self.update)  # Will call update once on widget creation
		
		# Add lines to canvas to use as markings to draw the clock face
		self.markings = []  # Create empty list
		marking_count = 12  # Set the number of markings
		for marking_id in range(marking_count):  # Iterate over the number of markings
			width = ((marking_id % 3) == 0) * 2 + 1  # Every 3rd marker is 3 pixels wide
			self.markings.append(
				self.canvas.create_line(0, 0, 0, 0, fill = '#000000', width = width)  #
			)

		# Add lines to canvas to use as hands
		self.hands = {
			'h': self.canvas.create_line(0, 0, 0, 0, fill = '#000000', width = 5),
			'm': self.canvas.create_line(0, 0, 0, 0, fill = '#000000', width = 3),
			's': self.canvas.create_line(0, 0, 0, 0, fill = '#ff0000', width = 1)
		}

		self.size = (0, 0)
		

	def update(self, size = None):
		# Draw an analogue clock on the canvas
		now = time.localtime() # Get the localized time as named tuple [^1]
		self.draw_analogue_clock(now.tm_hour, now.tm_min, now.tm_sec)

		# Call the function again at the next full second
		now = time.time()  # Get the time as float
		delay = 1000 - int(now * 1000) % 1000 # Number of milliseconds until next full second
		self.canvas.after(delay, self.update)

		
	def draw_analogue_clock(self, hours, minutes, seconds):
		# Get canvas dimensions
		width = self.canvas.winfo_width()
		height = self.canvas.winfo_height()

		# Check if the dimensions have changed
		resized = self.size != (width, height)
		self.size = (width, height)

		# Calculate clock dimensions
		radius = min(width, height) / 2
		center_x = width / 2
		center_y =  height / 2

		# Define a (sub)function to calculate the start and end coordinates for all lines
		# 'max_value' determines the full circle (= 360-degree or 2π radians)
		# 'value' determines the angle
		def xy(value, length = 1):
			angle = value * 2 * math.pi
			if length > 0:  # Draw the line from the center
				xy = (
					round(center_x),
					round(center_y),
					round(center_x + math.sin(angle) * radius * length),
					round(center_y - math.cos(angle) * radius * length))
			else:  # Draw the line from the perimeter
				xy = (
					round(center_x + math.sin(angle) * radius * (1 + length)),
					round(center_y - math.cos(angle) * radius * (1 + length)),
					round(center_x + math.sin(angle) * radius),
					round(center_y - math.cos(angle) * radius))
			return xy

		if resized:
			# Move markings
			marking_count = len(self.markings)
			for marking_id, marking in enumerate(self.markings):
				value = marking_id * 60 / marking_count
				self.canvas.coords(marking, *xy(value / 60, -0.20))
		
		# Move hands
		minutes += seconds / 60
		hours += minutes / 60
		self.canvas.coords(self.hands['h'], *xy(hours / 12, 0.5))
		self.canvas.coords(self.hands['m'], *xy(minutes / 60, 0.75))
		self.canvas.coords(self.hands['s'], *xy(seconds / 60))

# Inititalize Tkinter by creating a Tk root widget (= a window with a title bar and other decoration provided by your operating system)
root = tkinter.Tk()


subclassed_frame = AnalogueClock(root)

# Enter the Tkinter event loop
root.mainloop()

"""
[^1]: The Python Standard Library - Time access and conversions
      https://docs.python.org/3.7/library/time.html#time.struct_time

      Index | Attribute | Values
      ======+===========+=======
          0 | tm_year   | (for example, 1993)
          1 | tm_mon    | range [1, 12]
          2 | tm_mday   | range [1, 31]
          3 | tm_hour   | range [0, 23]
          4 | tm_min    | range [0, 59]
          5 | tm_sec    | range [0, 61]; see (2) in strftime() description
          6 | tm_wday   | range [0, 6], Monday is 0
          7 | tm_yday   | range [1, 366]
          8 | tm_isdst  | 0, 1 or -1; see below
        N/A | tm_zone   | abbreviation of timezone name
        N/A | tm_gmtoff | offset east of UTC in seconds
"""