2019-11-30 01:27:40 +00:00
|
|
|
#!/usr/bin/python3
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
import PySimpleGUI as sg
|
|
|
|
|
|
|
|
"""
|
|
|
|
DESIGN PATTERN - Multithreaded Long Tasks GUI using shared global variables
|
2020-03-05 21:18:28 +00:00
|
|
|
|
2019-11-30 01:27:40 +00:00
|
|
|
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
|
2019-11-30 03:01:50 +00:00
|
|
|
The "long work" is contained in the thread that is being started. Communicating is done (carefully) using global variables
|
2019-11-30 01:27:40 +00:00
|
|
|
|
2020-03-05 21:18:28 +00:00
|
|
|
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.
|
2019-11-30 01:27:40 +00:00
|
|
|
"""
|
|
|
|
|
2020-03-05 21:18:28 +00:00
|
|
|
|
2020-07-18 17:42:32 +00:00
|
|
|
def long_operation_thread(seconds, window):
|
2019-11-30 01:27:40 +00:00
|
|
|
"""
|
2020-03-05 21:18:28 +00:00
|
|
|
A worker thread that communicates with the GUI through a global message variable
|
2019-11-30 01:27:40 +00:00
|
|
|
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
|
|
|
|
"""
|
2020-07-18 17:42:32 +00:00
|
|
|
progress = 0
|
2019-11-30 03:01:50 +00:00
|
|
|
print('Thread started - will sleep for {} seconds'.format(seconds))
|
2020-03-05 21:18:28 +00:00
|
|
|
for i in range(int(seconds * 10)):
|
|
|
|
time.sleep(.1) # sleep for a while
|
2020-07-18 17:42:32 +00:00
|
|
|
progress += 100 / (seconds * 10)
|
|
|
|
window.write_event_value('-PROGRESS-', progress)
|
2019-11-30 01:27:40 +00:00
|
|
|
|
2020-07-18 17:42:32 +00:00
|
|
|
window.write_event_value('-THREAD-', '*** The thread says.... "I am finished" ***')
|
2019-11-30 01:27:40 +00:00
|
|
|
|
|
|
|
def the_gui():
|
|
|
|
"""
|
|
|
|
Starts and executes the GUI
|
|
|
|
Reads data from a global variable and displays
|
|
|
|
Returns when the user exits / closes the window
|
|
|
|
"""
|
|
|
|
|
2019-12-24 23:52:47 +00:00
|
|
|
sg.theme('Light Brown 3')
|
2019-11-30 01:27:40 +00:00
|
|
|
|
|
|
|
layout = [[sg.Text('Long task to perform example')],
|
2020-07-18 17:42:32 +00:00
|
|
|
[sg.MLine(size=(80, 12), k='-ML-', reroute_stdout=True,write_only=True, autoscroll=True, auto_refresh=True)],
|
2019-11-30 01:27:40 +00:00
|
|
|
[sg.Text('Number of seconds your task will take'),
|
2020-07-18 17:42:32 +00:00
|
|
|
sg.Input(key='-SECONDS-', focus=True, size=(5, 1)),
|
2020-03-05 21:18:28 +00:00
|
|
|
sg.Button('Do Long Task', bind_return_key=True),
|
|
|
|
sg.CBox('ONE chunk, cannot break apart', key='-ONE CHUNK-')],
|
2020-07-18 17:42:32 +00:00
|
|
|
[sg.Text('Work progress'), sg.ProgressBar(100, size=(20, 20), orientation='h', key='-PROG-')],
|
2019-11-30 01:27:40 +00:00
|
|
|
[sg.Button('Click Me'), sg.Button('Exit')], ]
|
|
|
|
|
2020-07-18 17:42:32 +00:00
|
|
|
window = sg.Window('Multithreaded Demonstration Window', layout, finalize=True)
|
2019-11-30 03:01:50 +00:00
|
|
|
|
2020-07-18 17:42:32 +00:00
|
|
|
timeout = thread = None
|
2019-11-30 01:27:40 +00:00
|
|
|
# --------------------- EVENT LOOP ---------------------
|
|
|
|
while True:
|
2020-07-18 17:42:32 +00:00
|
|
|
event, values = window.read(timeout=timeout)
|
|
|
|
# print(event, values)
|
2020-05-07 10:22:59 +00:00
|
|
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
2019-11-30 01:27:40 +00:00
|
|
|
break
|
2020-03-05 21:18:28 +00:00
|
|
|
elif event.startswith('Do') and not thread:
|
|
|
|
print('Thread Starting! Long work....sending value of {} seconds'.format(float(values['-SECONDS-'])))
|
2020-07-18 17:42:32 +00:00
|
|
|
timeout = 100 if values['-ONE CHUNK-'] else None
|
|
|
|
thread = threading.Thread(target=long_operation_thread, args=(float(values['-SECONDS-']),window), daemon=True)
|
2020-03-05 21:18:28 +00:00
|
|
|
thread.start()
|
2020-07-18 17:42:32 +00:00
|
|
|
if values['-ONE CHUNK-']:
|
|
|
|
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
|
2019-11-30 01:27:40 +00:00
|
|
|
elif event == 'Click Me':
|
|
|
|
print('Your GUI is alive and well')
|
2020-07-18 17:42:32 +00:00
|
|
|
elif event == '-PROGRESS-':
|
|
|
|
if not values['-ONE CHUNK-']:
|
|
|
|
window['-PROG-'].update_bar(values[event], 100)
|
|
|
|
elif event == '-THREAD-': # Thread has completed
|
2020-03-05 21:18:28 +00:00
|
|
|
thread.join(timeout=0)
|
2020-07-18 17:42:32 +00:00
|
|
|
print('Thread finished')
|
|
|
|
sg.popup_animated(None) # stop animination in case one is running
|
|
|
|
thread, message, progress, timeout = None, '', 0, None # reset variables for next run
|
|
|
|
window['-PROG-'].update_bar(0,0) # clear the progress bar
|
|
|
|
if values['-ONE CHUNK-'] and thread is not None:
|
|
|
|
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
|
2019-11-30 01:27:40 +00:00
|
|
|
window.close()
|
|
|
|
|
2020-03-05 21:18:28 +00:00
|
|
|
|
2019-11-30 01:27:40 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
the_gui()
|
|
|
|
print('Exiting Program')
|