|
5 | 5 | import PySimpleGUI as sg |
6 | 6 |
|
7 | 7 | """ |
| 8 | + You want to look for 3 points in this code, marked with comment "LOCATION X". |
| 9 | + 1. Where you put your call that takes a long time |
| 10 | + 2. Where the trigger to make the call takes place in the event loop |
| 11 | + 3. Where the completion of the call is indicated in the event loop |
| 12 | +
|
8 | 13 | Demo on how to add a long-running item to your PySimpleGUI Event Loop |
9 | 14 | If you want to do something that takes a long time, and you do it in the |
10 | 15 | main event loop, you'll quickly begin to see messages from windows that your |
|
17 | 22 | is spun off, allowed to work, and then gets back to the GUI when it's done working |
18 | 23 | on that task. |
19 | 24 | |
20 | | - If you have multiple long tasks to run, then you'll want a more sophisticated |
21 | | - format to your messages going back to the GUI so you'll know which task finished |
| 25 | + Every time you start up one of these long-running functions, you'll give it an "ID". |
| 26 | + When the function completes, it will send to the GUI Event Loop a message with |
| 27 | + the format: |
| 28 | + work_id ::: done |
| 29 | + This makes it easy to parse out your original work ID |
| 30 | + |
| 31 | + You can hard code these IDs to make your code more readable. For example, maybe |
| 32 | + you have a function named "update_user_list()". You can call the work ID "user list". |
| 33 | + Then check for the message coming back later from the work task to see if it starts |
| 34 | + with "user list". If so, then that long-running task is over. |
22 | 35 | |
23 | | - You want to look for 3 points in this code. |
24 | | - 1. Where you put your call that takes a long time |
25 | | - 2. Where the trigger to make the call takes place in the event loop |
26 | | - 3. Where the completion of the call is indicated in the event loop |
27 | 36 | """ |
28 | 37 |
|
29 | | -# Put your.... |
30 | | - |
31 | | - ###### ######## ## ## |
32 | | -## ## ## ## ## ## |
33 | | -## ## ## ## ## |
34 | | -## ######## ## ## |
35 | | -## ## ## ## |
36 | | -## ## ## ## ## |
37 | | - ###### ## ####### |
38 | | - |
39 | | -#### ## ## ######## ######## ## ## ###### #### ## ## ######## |
40 | | - ## ### ## ## ## ### ## ## ## ## ## ## ## |
41 | | - ## #### ## ## ## #### ## ## ## ## ## ## |
42 | | - ## ## ## ## ## ###### ## ## ## ###### ## ## ## ###### |
43 | | - ## ## #### ## ## ## #### ## ## ## ## ## |
44 | | - ## ## ### ## ## ## ### ## ## ## ## ## ## |
45 | | -#### ## ## ## ######## ## ## ###### #### ### ######## |
46 | | - |
47 | | - ###### ####### ######## ######## |
48 | | -## ## ## ## ## ## ## |
49 | | -## ## ## ## ## ## |
50 | | -## ## ## ## ## ###### |
51 | | -## ## ## ## ## ## |
52 | | -## ## ## ## ## ## ## |
53 | | - ###### ####### ######## ######## |
54 | | - |
55 | | -# Here in this thread |
56 | | - |
57 | | -def worker_thread(thread_name, gui_queue): |
58 | | - print('Starting thread - {} '.format(thread_name)) |
| 38 | +# ############################# User callable CPU intensive code ############################# |
| 39 | +# Put your long running code inside this "wrapper" |
| 40 | +# NEVER make calls to PySimpleGUI from this thread (or any thread)! |
| 41 | +# Create one of these functions for EVERY long-running call you want to make |
| 42 | +def long_function_wrapper(work_id, gui_queue): |
| 43 | + print('Thread starting - {} '.format(work_id)) |
59 | 44 | # LOCATION 1 |
60 | 45 | # this is our "long running function call" |
61 | 46 | time.sleep(5) # sleep for a while |
62 | | - print('Ending thread - {} '.format(thread_name)) |
63 | | - |
| 47 | + print('Thread Ending - {} '.format(work_id)) |
64 | 48 | # at the end of the work, before exiting, send a message back to the GUI indicating end |
65 | | - # in this case, we're using a simple string |
66 | | - gui_queue.put('{} - done'.format(thread_name)) # put a message into queue for GUI |
67 | | - |
| 49 | + gui_queue.put('{} ::: done'.format(work_id)) |
68 | 50 |
|
69 | | -######## ## ## #### |
70 | | -## ## ## ## ## |
71 | | -## ## ## ## |
72 | | -## #### ## ## ## |
73 | | -## ## ## ## ## |
74 | | -## ## ## ## ## |
75 | | -######## ######### #### |
76 | 51 |
|
| 52 | +############################# Begin GUI code ############################# |
77 | 53 | def the_gui(): |
78 | | - gui_queue = queue.Queue() # queue used to communicate between the gui and the threads |
| 54 | + gui_queue = queue.Queue() # queue used to communicate between the gui and long-running code |
79 | 55 |
|
80 | 56 | layout = [[sg.Text('Multithreaded Work Example')], |
| 57 | + [sg.Text('Click Go to start a long-running function call')], |
81 | 58 | [sg.Text('', size=(25, 1), key='_OUTPUT_')], |
82 | | - # [sg.Output(size=(40,6))], |
| 59 | + [sg.Text('', size=(25, 1), key='_OUTPUT2_')], |
83 | 60 | [sg.Button('Go'), sg.Button('Popup'), sg.Button('Exit')], ] |
84 | 61 |
|
85 | 62 | window = sg.Window('Multithreaded Window').Layout(layout) |
86 | 63 | # --------------------- EVENT LOOP --------------------- |
87 | | - count = 0 |
| 64 | + work_id = 0 |
88 | 65 | while True: |
89 | 66 | event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event |
90 | 67 | if event is None or event == 'Exit': |
91 | 68 | break |
92 | 69 | if event == 'Go': # clicking "Go" starts a long running work item by starting thread |
93 | | - window.Element('_OUTPUT_').Update('Starting long work %s'%count) |
| 70 | + window.Element('_OUTPUT_').Update('Starting long work %s'%work_id) |
94 | 71 | # LOCATION 2 |
95 | 72 | # STARTING long run by starting a thread |
96 | | - threading.Thread(target=worker_thread, args=('Thread %s'%count, gui_queue,), daemon=True).start() |
97 | | - count += 1 |
| 73 | + threading.Thread(target=long_function_wrapper, args=(work_id, gui_queue,), daemon=True).start() |
| 74 | + work_id += 1 |
98 | 75 | # --------------- Read next message coming in from threads --------------- |
99 | 76 | try: |
100 | | - message = gui_queue.get_nowait() # see if something has been posted to Queue |
101 | | - except queue.Empty: # get_nowait() will get exception when Queue is empty |
102 | | - message = None # nothing in queue so do nothing |
| 77 | + message = gui_queue.get_nowait() # see if something has been posted to Queue |
| 78 | + except queue.Empty: # get_nowait() will get exception when Queue is empty |
| 79 | + message = None # nothing in queue so do nothing |
103 | 80 |
|
104 | | - # if message received from queue, display the message in the Window |
| 81 | + # if message received from queue, then some work was completed |
105 | 82 | if message is not None: |
106 | 83 | # LOCATION 3 |
107 | 84 | # this is the place you would execute code at ENDING of long running task |
108 | | - window.Element('_OUTPUT_').Update(message) |
109 | | - |
| 85 | + # You can check the completed_work_id variable to see exactly which long-running function completed |
| 86 | + completed_work_id = message[:message.index(' :::')] |
| 87 | + window.Element('_OUTPUT2_').Update('Complete Work ID "{}"'.format(completed_work_id)) |
110 | 88 | if event == 'Popup': |
111 | 89 | sg.Popup('This is a popup showing that the GUI is running') |
112 | 90 | # if user exits the window, then close the window and exit the GUI func |
113 | 91 | window.Close() |
114 | 92 |
|
115 | | - |
116 | | -## ## ### #### ## ## |
117 | | -### ### ## ## ## ### ## |
118 | | -#### #### ## ## ## #### ## |
119 | | -## ### ## ## ## ## ## ## ## |
120 | | -## ## ######### ## ## #### |
121 | | -## ## ## ## ## ## ### |
122 | | -## ## ## ## #### ## ## |
| 93 | +############################# Main ############################# |
123 | 94 |
|
124 | 95 | if __name__ == '__main__': |
125 | 96 | the_gui() |
|
0 commit comments