diff --git a/DemoPrograms/Demo_Multithreaded_DataPump.py b/DemoPrograms/Demo_Multithreaded_DataPump.py new file mode 100644 index 00000000..55c96653 --- /dev/null +++ b/DemoPrograms/Demo_Multithreaded_DataPump.py @@ -0,0 +1,142 @@ +import PySimpleGUI as sg +import random +import time +import queue + +""" + Demo - Multi-threaded "Data Pump" Design Pattern + + Send data to your PySimpleGUI program through a Python Queue, enabling integration with many + different types of data sources. + + A thread gets data from a queue object and passes it over to the main event loop. + The external_thread is only used here to generaate random data. It's not part of the + overall "Design Pattern". + + The thread the_thread IS part of the design pattern. It reads data from the thread_queue and sends that + data over to the PySimpleGUI event loop. + + Copyright 2022 PySimpleGUI +""" + +gsize = (400, 400) # size of the graph + +THREAD_KEY = '-THREAD-' +THREAD_INCOMING_DATA = '-INCOMING DATA-' +THREAD_EXITNG = '-THREAD EXITING-' +THREAD_EXTERNAL_EXITNG = '-EXTERNAL THREAD EXITING-' + +# This queue is where you will send your data that you want to eventually arrive as an event +thread_queue = queue.Queue() + +# M""""""""M dP dP +# Mmmm mmmM 88 88 +# MMMM MMMM 88d888b. 88d888b. .d8888b. .d8888b. .d888b88 +# MMMM MMMM 88' `88 88' `88 88ooood8 88' `88 88' `88 +# MMMM MMMM 88 88 88 88. ... 88. .88 88. .88 +# MMMM MMMM dP dP dP `88888P' `88888P8 `88888P8 +# MMMMMMMMMM +# +# MP""""""`MM oo dP dP oo +# M mmmmm..M 88 88 +# M. `YM dP 88d8b.d8b. dP dP 88 .d8888b. d8888P dP 88d888b. .d8888b. +# MMMMMMM. M 88 88'`88'`88 88 88 88 88' `88 88 88 88' `88 88' `88 +# M. .MMM' M 88 88 88 88 88. .88 88 88. .88 88 88 88 88 88. .88 +# Mb. .dM dP dP dP dP `88888P' dP `88888P8 dP dP dP dP `8888P88 +# MMMMMMMMMMM .88 +# d8888P +# M""""""'YMM dP MP""""""`MM +# M mmmm. `M 88 M mmmmm..M +# M MMMMM M .d8888b. d8888P .d8888b. M. `YM .d8888b. dP dP 88d888b. .d8888b. .d8888b. +# M MMMMM M 88' `88 88 88' `88 MMMMMMM. M 88' `88 88 88 88' `88 88' `"" 88ooood8 +# M MMMM' .M 88. .88 88 88. .88 M. .MMM' M 88. .88 88. .88 88 88. ... 88. ... +# M .MM `88888P8 dP `88888P8 Mb. .dM `88888P' `88888P' dP `88888P' `88888P' +# MMMMMMMMMMM MMMMMMMMMMM +# + +def external_thread(thread_queue:queue.Queue): + """ + Represents some external source of data. + You would not include this code as a starting point with this Demo Program. Your data is assumed to + come from somewhere else. The important part is that you add data to the thread_queue + :param thread_queue: + :return: + """ + i = 0 + while True: + time.sleep(.01) + point = (random.randint(0,gsize[0]), random.randint(0,gsize[1])) + radius = random.randint(10, 40) + thread_queue.put((point, radius)) + i += 1 + + +# M""""""""M dP dP MM""""""""`M +# Mmmm mmmM 88 88 MM mmmmmmmM +# MMMM MMMM 88d888b. 88d888b. .d8888b. .d8888b. .d888b88 M' MMMM .d8888b. 88d888b. +# MMMM MMMM 88' `88 88' `88 88ooood8 88' `88 88' `88 MM MMMMMMMM 88' `88 88' `88 +# MMMM MMMM 88 88 88 88. ... 88. .88 88. .88 MM MMMMMMMM 88. .88 88 +# MMMM MMMM dP dP dP `88888P' `88888P8 `88888P8 MM MMMMMMMM `88888P' dP +# MMMMMMMMMM MMMMMMMMMMMM +# +# MM"""""""`YM MP""""""`MM MM'"""""`MM MM""""""""`M dP +# MM mmmmm M M mmmmm..M M' .mmm. `M MM mmmmmmmM 88 +# M' .M M. `YM M MMMMMMMM M` MMMM dP .dP .d8888b. 88d888b. d8888P .d8888b. +# MM MMMMMMMM MMMMMMM. M M MMM `M MM MMMMMMMM 88 d8' 88ooood8 88' `88 88 Y8ooooo. +# MM MMMMMMMM M. .MMM' M M. `MMM' .M MM MMMMMMMM 88 .88' 88. ... 88 88 88 88 +# MM MMMMMMMM Mb. .dM MM. .MM MM .M 8888P' `88888P' dP dP dP `88888P' +# MMMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM + + +def the_thread(window:sg.Window, thread_queue:queue.Queue): + """ + The thread that communicates with the application through the window's events. + Waits for data from a queue and sends that data on to the event loop + :param window: + :param thread_queue: + :return: + """ + + while True: + data = thread_queue.get() + window.write_event_value((THREAD_KEY, THREAD_INCOMING_DATA), data) # Data sent is a tuple of thread name and counter + + +def main(): + + layout = [ [sg.Text('My Simulated Data Pump')], + [sg.Multiline(size=(60, 20), k='-MLINE-')], + [sg.Graph(gsize, (0, 0), gsize, k='-G-', background_color='gray')], + [sg.Button('Go'), sg.Button('Exit')] ] + + window = sg.Window('Simulated Data Pump', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT) + + graph = window['-G-'] # type: sg.Graph + + while True: # Event Loop + event, values = window.read() + # print(event, values) + if event == sg.WIN_CLOSED or event == 'Exit': + break + if event == 'Go': + window.start_thread(lambda: the_thread(window, thread_queue), (THREAD_KEY, THREAD_EXITNG)) + window.start_thread(lambda: external_thread(thread_queue), (THREAD_KEY, THREAD_EXTERNAL_EXITNG)) + # Events coming from the Thread + elif event[0] == THREAD_KEY: + if event[1] == THREAD_INCOMING_DATA: + point, radius = values[event] + graph.draw_circle(point, radius=radius, fill_color='green') + window['-MLINE-'].print(f'Drawing at {point} radius {radius}', c='white on red') + elif event[1] == THREAD_EXITNG: + window['-MLINE-'].print('Thread has exited') + elif event[1] == THREAD_EXTERNAL_EXITNG: + window['-MLINE-'].print('Data Pump thread has exited') + if event == 'Edit Me': + sg.execute_editor(__file__) + elif event == 'Version': + sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True) + + window.close() + +if __name__ == '__main__': + main()