Merge pull request #2676 from PySimpleGUI/Dev-latest

Simplified the code. Removed need for at a flag.  Shortened quite a bit.
This commit is contained in:
PySimpleGUI 2020-03-05 16:18:50 -05:00 committed by GitHub
commit 337ba2bc17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 40 additions and 47 deletions

View File

@ -5,42 +5,38 @@ import PySimpleGUI as sg
"""
DESIGN PATTERN - Multithreaded Long Tasks GUI using shared global variables
Presents one method for running long-running operations in a PySimpleGUI environment.
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
The "long work" is contained in the thread that is being started. Communicating is done (carefully) using global variables
There are 2 ways "progress" is being reported to the user. If a the amount of time is known ahead of time and
the work can be broek down into countable units, then a progress bar is used. If a task is one long chunk
of time that cannot be broken down into smaller units, then an animated GIF is shown that spins as
long as the task is running.
There are 2 ways "progress" is being reported to the user.
You can simulate the 2 different scenarios that happen with worker threads.
1. If a the amount of time is known ahead of time or the work can be broken down into countable units, then a progress bar is used.
2. If a task is one long chunk of time that cannot be broken down into smaller units, then an animated GIF is shown that spins as
long as the task is running.
"""
thread_done = 1
message = ''
thread_counter = 0
total = 100
progress = 0
total = 100 # number of units that are used with the progress bar
message = '' # used by thread to send back a message to the main thread
progress = 0 # current progress up to a maximum of "total"
def long_operation_thread(seconds):
"""
A worker thread that communicates with the GUI through a global variable
A worker thread that communicates with the GUI through a global message variable
This thread can block for as long as it wants and the GUI will not be affected
:param seconds: (int) How long to sleep, the ultimate blocking call
:return:
"""
global thread_done, message, thread_counter, progress
global message, progress
print('Thread started - will sleep for {} seconds'.format(seconds))
thread_counter += 1
for i in range(int(seconds*10)):
time.sleep(.1) # sleep for a while
progress += total/(seconds*10)
message = f'***This is a message from the thread {thread_counter} ***'
thread_done = True
for i in range(int(seconds * 10)):
time.sleep(.1) # sleep for a while
progress += total / (seconds * 10)
message = f'*** The thread says.... "I am finished" ***'
def the_gui():
"""
@ -48,53 +44,50 @@ def the_gui():
Reads data from a global variable and displays
Returns when the user exits / closes the window
"""
global thread_done, message, progress
global message, progress
sg.theme('Light Brown 3')
layout = [[sg.Text('Long task to perform example')],
[sg.Output(size=(80, 12))],
[sg.Text('Number of seconds your task will take'),
sg.Input(key='-SECONDS-', size=(5, 1)),
sg.Button('Do Long Task', bind_return_key=True),
sg.CBox('ONE chunk, cannot break apart', key='-ONE CHUNK-')],
[sg.Text('Work progress'), sg.ProgressBar(total, size=(20,20), orientation='h', key='-PROG-')],
sg.Input(key='-SECONDS-', size=(5, 1)),
sg.Button('Do Long Task', bind_return_key=True),
sg.CBox('ONE chunk, cannot break apart', key='-ONE CHUNK-')],
[sg.Text('Work progress'), sg.ProgressBar(total, size=(20, 20), orientation='h', key='-PROG-')],
[sg.Button('Click Me'), sg.Button('Exit')], ]
window = sg.Window('Multithreaded Window', layout)
window = sg.Window('Multithreaded Demonstration Window', layout)
one_chunk = False
sg.popup_animated(None)
thread = None
# --------------------- EVENT LOOP ---------------------
while True:
event, values = window.read(timeout=100)
if event in (None, 'Exit'):
break
elif event.startswith('Do'):
seconds = float(values['-SECONDS-'])
one_chunk = values['-ONE CHUNK-']
print('Thread Starting! Long work....sending value of {} seconds'.format(seconds))
threading.Thread(target=long_operation_thread, args=(seconds, ), daemon=True).start()
elif event.startswith('Do') and not thread:
print('Thread Starting! Long work....sending value of {} seconds'.format(float(values['-SECONDS-'])))
thread = threading.Thread(target=long_operation_thread, args=(float(values['-SECONDS-']),), daemon=True)
thread.start()
elif event == 'Click Me':
print('Your GUI is alive and well')
# --------------- Check for incoming messages from threads ---------------
if thread_done is True:
print('The thread has finished!')
print(f'message = {message}')
# reset everything for the next run
sg.popup_animated(None) # just in case one's running, stop it
thread_done = one_chunk = False
message, progress = '', 0
window['-PROG-'].update_bar(total, total) # show the bar as maxed out
if one_chunk: # flag signifies the thread is taking to take one big chunk of time
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
elif progress != 0:
window['-PROG-'].update_bar(progress, total) # update the progress bar if non-zero
# if user exits the window, then close the window and exit the GUI func
if thread: # If thread is running
if values['-ONE CHUNK-']: # If one big operation, show an animated GIF
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
else: # Not one big operation, so update a progress bar instead
window['-PROG-'].update_bar(progress, total)
thread.join(timeout=0)
if not thread.is_alive(): # the thread finished
print(f'message = {message}')
sg.popup_animated(None) # stop animination in case one is running
thread, message, progress = None, '', 0 # reset variables for next run
window['-PROG-'].update_bar(0,0) # clear the progress bar
window.close()
if __name__ == '__main__':
the_gui()
print('Exiting Program')