I have a project where I'm trying to build a simple time-clock using Python 3 on a Raspberry Pi. The Pi is running 64-bit Bullseye.

In my script, I create a window with two columns, one for entry and one for display. The entry side is working (as far as I have gone). The display side sorta works, and this is the issue.

The user will enter their code, press "Enter" to see their information, and then press "In" or "Out" for clocking in or out. When the user presses "In", I want to display a message that says either "Clocked In" or "Already Clocked in".The issue is that the clocked-in message does not display. The statement that fails is the msg_elem.Update( .... If I run the Python debugger, the message is displayed, but not in "normal" running.My question is What am I doing wrong?

This is a working example...

import sys, os, platformimport PySimpleGUI as sgfrom PIL import Image, ImageTkfrom time import sleepimport io## Elements# create the Elements we want to control outside the formout_elem = sg.Text('', size=(55, 1), font=('Helvetica', 18), text_color='black',justification='center')in_elem = sg.Input(size=(10,1), do_not_clear=True)img_elem = sg.Image(size=(240,240),key="-IMAGE-")msg_elem = sg.Text('', size=(65, 1), font=('Helvetica', 18), text_color='black',justification='center',key='-MSG-')## Columnsbutton_column = [[sg.Text("User Input"),in_elem],[sg.ReadFormButton('1', size=(3,3)),sg.ReadFormButton('2', size=(3,3)),sg.ReadFormButton('3', size=(3,3)),sg.ReadFormButton('Clear', size=(6,3))],[sg.ReadFormButton('4', size=(3,3)),sg.ReadFormButton('5', size=(3,3)),sg.ReadFormButton('6', size=(3,3)),sg.ReadFormButton('Enter', size=(6,3))],[sg.ReadFormButton('7', size=(3,3)),sg.ReadFormButton('8', size=(3,3)),sg.ReadFormButton('9', size=(3,3)),sg.ReadFormButton('Quit', size=(6,3))],[sg.T(''), sg.T(' ' * 8),sg.ReadFormButton('0', size=(3,3))],[sg.T('')],[sg.ReadFormButton('In', size=(13,3)),sg.ReadFormButton('Out', size=(13,3)),]]display_column = [[sg.Text("User Details")],[out_elem],[sg.T(' ' * 30), img_elem],[msg_elem],]#layout = [[sg.Column(button_column),sg.VSeperator(),sg.Column(display_column,justification='center',vertical_alignment='top')]]form = sg.Window('Time Clock', layout, auto_size_buttons=False, size=(800,480))keys_entered = ''while True:button, values = form.Read()if button is None:form["-IMAGE-"].update()out_elem.Update( " " )breakelif button == 'Clear':keys_entered = ''pcpid = ''empid = ''form["-IMAGE-"].update()in_elem.Update(keys_entered)out_elem.Update( " " )msg_elem.Update( " " )elif button in '1234567890':keys_entered = in_elem.Get()keys_entered += buttonelif button == 'Enter':keys_entered = '123'first_name = 'Mike'last_name = 'Retiredguy'empid = 12345im1 = Image.open( 'mike.png' )im1.thumbnail((240,240))bio = io.BytesIO()im1.save( bio, format="PNG")empimage = bio.getvalue()form["-IMAGE-"].update( empimage )dsplAns = f"{empid} - {last_name}, {first_name}"out_elem.Update( dsplAns )elif button == 'In':# import pdb; pdb.set_trace()sqlAns = 1 # User already clocked inif sqlAns > 0:msg_elem.Update( "...is already clocked in! (A)" ) # <=== THIS IS WHAT FAILSelse:msg_elem.Update( "...is clocked in! (B)" ) # <=== THIS IS WHAT FAILSsleep(10)# Clear inputkeys_entered = ''pcpid = ''empid = ''form["-IMAGE-"].update()in_elem.Update(keys_entered)out_elem.Update( " " )msg_elem.Update( " " )elif button == 'Quit':sys.exit(0)in_elem.Update(keys_entered)## ###EOF###

I have tried this on the RPi, and on a Virtual system with Debian (not Pi) linux. Both give me the same result. I've searched here on Stack Overflow, and Google in general, and I think I'm doing it correctly, but failing.

2

Best Answer


The method msg_elem.Update just update the architecture of PySimpleGUI, not the GUI. Need to call window.refresh() before sleep(10) if you want the GUI updated immediately, not until back to window.read(). sleep(10) take long time for GUI to wait, so it will show "Not Responding".

Demo Code

from time import sleepimport threadingimport PySimpleGUI as sgdef func(window, value):global runningmessage = f'You clicked the button "{value}", this message will be cleared after 3s !'window.write_event_value('Update', message)sleep(3)window.write_event_value('Update', '')running = Falsesg.set_options(font=('Courier New', 12))layout = [[sg.Button('Hello'), sg.Button('World')],[sg.Text('', size=80, key='State')],]window = sg.Window('Title', layout)running = Falsewhile True:event, values = window.read()if event == sg.WIN_CLOSED:breakelif event in ('Hello', 'World') and not running:running = Truethreading.Thread(target=func, args=(window, event), daemon=True).start()elif event == 'Update':message = values[event]window['State'].update(message)window.close()

Here is a solution based on Jason's solution that uses the PySimpleGUI timer capability to simplify the code so that threads are not required. As of the date of this post, you'll need to get the GitHub version of PySimpleGUI to use Window.timer_start() and Window.timer_stop_all(). You could also use a flag to protect against multiple button clicks if desired.

The Demo Program Demo_Window_Timer.py shows how to use this set of API calls in more detail and can be found with the other Demo Programs on GitHub.

The concept is the same as the previous answer. A timer is started when the button is clicked. To handle multiple button clicks, previous timers are canceled when a new button click happens.

The important line of code that starts the timer is:

window.timer_start(3000, repeating=False) # start a 3-second timer

When the timer expires, you'll get an event that you can specify when you start the timer, or if none is specified, the default key value will be used. That's what was done in this example. Detecting the timer has expired is this line:

 elif event == sg.EVENT_TIMER:

The complete program...

import PySimpleGUI as sgsg.set_options(font=('Courier New', 12))layout = [[sg.Button('Hello'), sg.Button('World')],[sg.Text('', size=80, key='State')],]window = sg.Window('Title', layout)while True:event, values = window.read()if event == sg.WIN_CLOSED:breakelif event in ('Hello', 'World'):window.timer_stop_all() # stop any previous timerswindow['State'].update(f'You clicked the button "{event}", this message will be cleared after 3s !')window.timer_start(3000, repeating=False) # start a 3-second timerelif event == sg.EVENT_TIMER:window['State'].update('')window.close()