2

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, platform
import PySimpleGUI as sg
from PIL import Image, ImageTk
from time import sleep
import io

#
# Elements
# create the Elements we want to control outside the form
out_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-')

#
# Columns
button_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( " " )
        break
    elif 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 += button
    elif button == 'Enter':
        keys_entered   = '123'
        first_name     = 'Mike'
        last_name      = 'Retiredguy'
        empid          = 12345
        im1            = 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 in
        if sqlAns > 0:
            msg_elem.Update( "...is already clocked in! (A)" )      # <===  THIS IS WHAT FAILS
        else:
            msg_elem.Update( "...is clocked in! (B)" )          # <===  THIS IS WHAT FAILS
        sleep(10)
# Clear input
        keys_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.

  • 1
    There's a lot going on in your code above. Can you reduce it to a shorter example of the question/problem? For example replace the database access with hardcoded test data. – Hugh W Feb 18 '23 at 16:10
  • For more information on doing so, see [MRE]. The code as shown also has a syntax error (a missing `"`) that would prevent it from even running. – CrazyChucky Feb 18 '23 at 16:14
  • I have tried to clean up the code. The thing is, I don't know enough to know what is important. I think the database stuff is ok. It's the PySimpleGUI code that is causing the issue. I think. – RetiredMike Feb 18 '23 at 17:36
  • Please check that the code you post a) runs and b) produces the incorrect behavior you describe. If we can copy, paste, and test it, we have a much better chance of being able to help you. – CrazyChucky Feb 18 '23 at 18:35
  • 1
    You're using REALLY REALLY old calls. Not sure where you're getting the starting code from. The methods being called should start with a lower case letter (PEP8 compliant). So `update` for example. You should be using `Button` for your buttons. The older calls are there for backwards compatibility but not a good idea to use them. Never put a sleep in your event loop. You shouldn't need to call `get` for any element. The `values` dictionary already has the value. Maybe look at the eCookbook, cookbook and demo programs for some examples that may help. Use keys for all of your inputs. – Mike from PSG Feb 18 '23 at 19:28
  • Hey Everybody... I appreciate the suggestions, but right now I'd like to know what I'm doing wrong. The code now executes with "python3 example.py" on my Rpi400. Style points are not important yet. The keypad code came from a source on Github. – RetiredMike Feb 19 '23 at 17:28
  • Could there be something going wrong with the line just above that: "elif button == 'In':"? Pretty hard to understand why it woudn't work otherwise. – gerald Feb 19 '23 at 22:58
  • 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". – Jason Yang Feb 20 '23 at 01:58
  • Yes! Thank you Jason. That is what I was missing. Now on to the style and more modern codes. – RetiredMike Feb 20 '23 at 02:16
  • @JasonYang Could you post that as an answer? – CrazyChucky Feb 20 '23 at 12:11

2 Answers2

2

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 sleep
import threading
import PySimpleGUI as sg

def func(window, value):
    global running
    message = 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 = False

sg.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 = False

while True:

    event, values = window.read()

    if event == sg.WIN_CLOSED:
        break
    elif event in ('Hello', 'World') and not running:
        running = True
        threading.Thread(target=func, args=(window, event), daemon=True).start()
    elif event == 'Update':
        message = values[event]
        window['State'].update(message)

window.close()
Jason Yang
  • 11,284
  • 2
  • 9
  • 23
1

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 sg

sg.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:
        break
    elif event in ('Hello', 'World'):
        window.timer_stop_all()                         # stop any previous timers
        window['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 timer
    elif event ==  sg.EVENT_TIMER:
        window['State'].update('')

window.close()
Mike from PSG
  • 5,312
  • 21
  • 39