From b5c0bd735dff63a6122f253a0c6f37adc4949a46 Mon Sep 17 00:00:00 2001 From: PySimpleGUI Date: Tue, 28 Jul 2020 14:36:23 -0400 Subject: [PATCH] Documented the new Window.write_event_value, new key error recovery --- docs/call reference.md | 66 +++++++++--- docs/index.md | 234 +++++++++++++++++++++++++++++++++++++++-- readme.md | 234 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 506 insertions(+), 28 deletions(-) diff --git a/docs/call reference.md b/docs/call reference.md index 6d11ddef..6ef7c941 100644 --- a/docs/call reference.md +++ b/docs/call reference.md @@ -3180,7 +3180,9 @@ Parameter Descriptions: ### Update -Changes some of the settings for the Image Element. Must call `Window.Read` or `Window.Finalize` prior +Changes some of the settings for the Image Element. Must call `Window.Read` or `Window.Finalize` prior. +To clear an image that's been displayed, call with NONE of the options set. A blank update call will +delete the previously shown image. ``` Update(filename=None, @@ -3349,7 +3351,9 @@ unhide_row() ### update -Changes some of the settings for the Image Element. Must call `Window.Read` or `Window.Finalize` prior +Changes some of the settings for the Image Element. Must call `Window.Read` or `Window.Finalize` prior. +To clear an image that's been displayed, call with NONE of the options set. A blank update call will +delete the previously shown image. ``` update(filename=None, @@ -4313,6 +4317,7 @@ Multiline(default_text="", font=None, pad=None, tooltip=None, + justification=None, right_click_menu=None, visible=True, metadata=None) @@ -4401,7 +4406,8 @@ Update(value=None, text_color_for_value=None, background_color_for_value=None, visible=None, - autoscroll=None) + autoscroll=None, + justification=None) ``` Parameter Descriptions: @@ -4489,7 +4495,8 @@ print(args=*<1 or N object>, end=None, sep=None, text_color=None, - background_color=None) + background_color=None, + justification=None) ``` Parameter Descriptions: @@ -4618,7 +4625,8 @@ update(value=None, text_color_for_value=None, background_color_for_value=None, visible=None, - autoscroll=None) + autoscroll=None, + justification=None) ``` Parameter Descriptions: @@ -5394,7 +5402,7 @@ ProgressBar(max_value, orientation=None, size=(None, None), auto_size_text=None, - bar_color=(None, None), + bar_color=None, style=None, border_width=None, relief=None, @@ -5413,7 +5421,7 @@ Parameter Descriptions: | str | orientation | 'horizontal' or 'vertical' | | (int, int) | size | Size of the bar. If horizontal (chars wide, pixels high), vert (pixels wide, rows high) | | bool | auto_size_text | Not sure why this is here | -| Tuple[str, str] | bar_color | The 2 colors that make up a progress bar. One is the background, the other is the bar | +| Tuple[str, str] or str | bar_color | The 2 colors that make up a progress bar. Easy to remember which is which if you say "ON" between colors. "red" on "green". | | str | style | Progress bar style defined as one of these 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative' | | int | border_width | The amount of pixels that go around the outside of the bar | | str | relief | relief style. Values are same as progress meter relief values. Can be a constant or a string: `RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID` (Default value = DEFAULT_PROGRESS_BAR_RELIEF) | @@ -5454,19 +5462,26 @@ Parameter Descriptions: ### Update Changes some of the settings for the ProgressBar Element. Must call `Window.Read` or `Window.Finalize` prior +Now has the ability to modify the count so that the update_bar method is not longer needed separately ``` -Update(visible=None) +Update(current_count, + max=None, + visible=None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| bool | visible | control visibility of element | +| int | current_count | sets the current value | +| int | max | changes the max value | +| bool | visible | control visibility of element | +| (bool) | **RETURN** | Returns True if update was OK. False means something wrong with window or it was closed ### UpdateBar +DEPRECATED BUT STILL USABLE - has been combined with the normal ProgressBar.update method. Change what the bar shows by changing the current count and optionally the max count ``` @@ -5616,19 +5631,26 @@ unhide_row() ### update Changes some of the settings for the ProgressBar Element. Must call `Window.Read` or `Window.Finalize` prior +Now has the ability to modify the count so that the update_bar method is not longer needed separately ``` -update(visible=None) +update(current_count, + max=None, + visible=None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| bool | visible | control visibility of element | +| int | current_count | sets the current value | +| int | max | changes the max value | +| bool | visible | control visibility of element | +| (bool) | **RETURN** | Returns True if update was OK. False means something wrong with window or it was closed ### update_bar +DEPRECATED BUT STILL USABLE - has been combined with the normal ProgressBar.update method. Change what the bar shows by changing the current count and optionally the max count ``` @@ -8688,6 +8710,7 @@ Window(title, ttk_theme=None, use_ttk_buttons=None, modal=False, + subwindow=False, metadata=None) ``` @@ -11153,7 +11176,8 @@ cprint(args=*<1 or N object>, colors=None, c=None, window=None, - key=None) + key=None, + justification=None) ``` Parameter Descriptions: @@ -11697,7 +11721,7 @@ Parameter Descriptions: | bool | keep_on_top | If True the window will remain above all current windows | | Tuple[int, int] | location | (x,y) Location on screen to display the upper left corner of window | | str) or (bytes | image | Image to include at the top of the popup window | -| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = False | +| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = True | | Union[str, None] | **RETURN** | Text entered or None if window was closed or cancel button clicked Display a Popup without a titlebar. Enables grab anywhere so you can move it @@ -12746,7 +12770,7 @@ Parameter Descriptions: | bool | keep_on_top | If True the window will remain above all current windows | | Tuple[int, int] | location | (x,y) Location on screen to display the upper left corner of window | | str) or (bytes | image | Image to include at the top of the popup window | -| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = False | +| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = True | | Union[str, None] | **RETURN** | Text entered or None if window was closed or cancel button clicked Display a Popup without a titlebar. Enables grab anywhere so you can move it @@ -13547,6 +13571,8 @@ set_options(icon=None, use_ttk_buttons=None, ttk_theme=None, suppress_error_popups=None, + suppress_raise_key_errors=None, + suppress_key_guessing=None, enable_treeview_869_patch=None) ``` @@ -13591,6 +13617,9 @@ Parameter Descriptions: | bool | use_ttk_buttons | if True will cause all buttons to be ttk buttons | | str | ttk_theme | Theme to use with ttk widgets. Choices (on Windows) include - 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative' | | bool | suppress_error_popups | If True then error popups will not be shown if generated internally to PySimpleGUI | +| bool | suppress_raise_key_errors | If True then key errors won't be raised (you'll still get popup error) | +| bool | suppress_key_guessing | If True then key errors won't try and find closest matches for you | +| bool | enable_treeview_869_patch | If True, then will use the treeview color patch for tk 8.6.9 | | None | **RETURN** | None ### Non PEP8 version (same as PEP8 version) @@ -13647,6 +13676,8 @@ SetOptions(icon=None, use_ttk_buttons=None, ttk_theme=None, suppress_error_popups=None, + suppress_raise_key_errors=None, + suppress_key_guessing=None, enable_treeview_869_patch=None) ``` @@ -13691,6 +13722,9 @@ Parameter Descriptions: | bool | use_ttk_buttons | if True will cause all buttons to be ttk buttons | | str | ttk_theme | Theme to use with ttk widgets. Choices (on Windows) include - 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative' | | bool | suppress_error_popups | If True then error popups will not be shown if generated internally to PySimpleGUI | +| bool | suppress_raise_key_errors | If True then key errors won't be raised (you'll still get popup error) | +| bool | suppress_key_guessing | If True then key errors won't try and find closest matches for you | +| bool | enable_treeview_869_patch | If True, then will use the treeview color patch for tk 8.6.9 | | None | **RETURN** | None ## The Test Harness @@ -13872,6 +13906,7 @@ They are sorted alphabetically. The legacy color names are mixed in, but otherw theme_previewer(columns=12, scrollable=False, scroll_area_size=(None, None), + search_string=None, location=(None, None)) ``` @@ -13882,6 +13917,7 @@ Parameter Descriptions: | int | columns | The number of themes to display per row | | bool | scrollable | If True then scrollbars will be added | | (int, int) | scroll_area_size | Size of the scrollable area (The Column Element used to make scrollable) | +| str | search_string | If specified then only themes containing this string will be shown | | (int, int) | location | Location on the screen to place the window. Defaults to the center like all windows | Sets/Returns the progress meter border width currently in use @@ -14032,6 +14068,7 @@ They are sorted alphabetically. The legacy color names are mixed in, but otherw preview_all_look_and_feel_themes(columns=12, scrollable=False, scroll_area_size=(None, None), + search_string=None, location=(None, None)) ``` @@ -14042,6 +14079,7 @@ Parameter Descriptions: | int | columns | The number of themes to display per row | | bool | scrollable | If True then scrollbars will be added | | (int, int) | scroll_area_size | Size of the scrollable area (The Column Element used to make scrollable) | +| str | search_string | If specified then only themes containing this string will be shown | | (int, int) | location | Location on the screen to place the window. Defaults to the center like all windows | Get a list of the valid values to pass into your call to change_look_and_feel diff --git a/docs/index.md b/docs/index.md index de8794aa..803e4a34 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1669,7 +1669,7 @@ Parameter Descriptions: | bool | keep_on_top | If True the window will remain above all current windows | | Tuple[int, int] | location | (x,y) Location on screen to display the upper left corner of window | | str) or (bytes | image | Image to include at the top of the popup window | -| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = False | +| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = True | | Union[str, None] | **RETURN** | Text entered or None if window was closed or cancel button clicked ```python @@ -2588,15 +2588,91 @@ You have a couple of options for dealing this with. If your operation can be br If, on the other hand, your operation is not under your control or you are unable to add `Refresh` calls, then the next option available to you is to move your long operations into a thread. +### The "Old Way" + There are a couple of demo programs available for you to see how to do this. You basically put your work into a thread. When the thread is completed, it tells the GUI by sending a message through a queue. The event loop will run with a timer set to a value that represents how "responsive" you want your GUI to be to the work completing. -These 2 demo programs are called -```python -Demo_Threaded_Work.py - Best documented. Single thread used for long task -Demo_Multithreaded_Long_Tasks.py - Similar to above, but with less fancy GUI. Allows you to set amount of time -``` +### The "New Way" - `Window.write_event_value` -These 2 particular demos have a LOT of comments showing you where to add your code, etc.. The amount of code to do this is actually quite small and you don't need to understand the mechanisms used if you simply follow the demo that's been prepared for you. +This new function that is available currently only in the tkinter port as of July 2020 is exciting and represents the future way multi-threading will be handled in PySimpleGUI (or so is hoped). + +Previously, a queue was used where your event loop would **poll** for incoming messages from a thread. + +Now, threads can directly inject events into a Window so that it will show up in the `window.read()` calls. This allows a your event loop to "pend", waiting for normal window events as well as events being generated by threads. + +You can see this new capability in action in this demo: Demo_Multithreaded_Write_Event_Value.py + +Here is that program for your inspection and education. It's SO nice to no longer poll for threaded events. + +```python +import threading +import time +import PySimpleGUI as sg + +""" + Threaded Demo - Uses Window.write_event_value communications + + Requires PySimpleGUI.py version 4.25.0 and later + + This is a really important demo to understand if you're going to be using multithreading in PySimpleGUI. + + Older mechanisms for multi-threading in PySimpleGUI relied on polling of a queue. The management of a communications + queue is now performed internally to PySimpleGUI. + + The importance of using the new window.write_event_value call cannot be emphasized enough. It will hav a HUGE impact, in + a positive way, on your code to move to this mechanism as your code will simply "pend" waiting for an event rather than polling. + + Copyright 2020 PySimpleGUI.org +""" + +THREAD_EVENT = '-THREAD-' + +cp = sg.cprint + +def the_thread(window): + """ + The thread that communicates with the application through the window's events. + + Once a second wakes and sends a new event and associated value to the window + """ + i = 0 + while True: + time.sleep(1) + window.write_event_value('-THREAD-', (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter + cp('This is cheating from the thread', c='white on green') + i += 1 + +def main(): + """ + The demo will display in the multiline info about the event and values dictionary as it is being + returned from window.read() + Every time "Start" is clicked a new thread is started + Try clicking "Dummy" to see that the window is active while the thread stuff is happening in the background + """ + + layout = [ [sg.Text('Output Area - cprint\'s route to here', font='Any 15')], + [sg.Multiline(size=(65,20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)], + [sg.T('Input so you can see data in your dictionary')], + [sg.Input(key='-IN-', size=(30,1))], + [sg.B('Start A Thread'), sg.B('Dummy'), sg.Button('Exit')] ] + + window = sg.Window('Window Title', layout, finalize=True) + + while True: # Event Loop + event, values = window.read() + cp(event, values) + if event == sg.WIN_CLOSED or event == 'Exit': + break + if event.startswith('Start'): + threading.Thread(target=the_thread, args=(window,), daemon=True).start() + if event == THREAD_EVENT: + cp(f'Data from the thread ', colors='white on purple', end='') + cp(f'{values[THREAD_EVENT]}', colors='white on red') + window.close() + +if __name__ == '__main__': + main() +``` ### Multithreaded Programs @@ -3360,6 +3436,150 @@ Then to turn off return values for that element, the `Multiline` element would b sg.Multiline(size=(40,8), key='-MLINE-' + sg.WRITE_ONLY_KEY) ``` +## Key Errors - Key error recovery algorithm + +In the primary (tkinter) port of PySimpleGUI, starting in version 4.27.0 (not yet on PyPI... but available on GitHub as 4.26.0.14+) + +There are now 3 controls over key error handling and a whole new era of key reporting. + +```python +SUPPRESS_ERROR_POPUPS = False +SUPPRESS_RAISE_KEY_ERRORS = False +SUPPRESS_KEY_GUESSING = False +``` + +You can modify these values by calling `set_options`. + +```python + sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=False, suppress_key_guessing=False) +``` + +A basic definition of them are: +`suppress_error_popups` - Disables error popups that are generated within PySimpleGUI itself to not be shown +`suppress_raise_key_errors` - Disables raising a key error if a key or a close match are not found +`suppress_key_guessing` - Disables the key guessing algorithm should you have a key error + +With the defaults left as defined (all `False`), here is how key errors work. + +This is the program being used in this example: + +```python +import PySimpleGUI as sg + +def main(): + sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=False, suppress_key_guessing=False) + + layout = [ [sg.Text('My Window')], + [sg.Input(k='-IN-'), sg.Text(size=(12,1), key='-OUT-')], + [sg.Button('Go'), sg.Button('Exit')] ] + + window = sg.Window('Window Title', layout, finalize=True) + + while True: # Event Loop + event, values = window.read() + print(event, values) + if event == sg.WIN_CLOSED or event == 'Exit': + break + window['-O U T'].update(values['-IN-']) + window.close() + +def func(): + + main() + +func() +``` + +A few things to note about it: + +* There are multiple levels of functions being called, not just a flat program +* There are 2 keys explicitly defined, both are text at this point (we'll change them later) +* There are 2 lookups happening, one with `window` the other with `values` + +This key error recovery algorithm only applies to element keys being used to lookup keys inside of windows. The `values` key lookup is a plain dictionary and so nothing fancy is done for that lookup. + +### Example 1 - Simple text string misspelling + +In our example, this line of code has an error: + +```python +window['-O U T'].update(values['-IN-']) +``` + +There are multiple problems with the key `'-OUT-'`. It is missing a dash and it has a bunch of extra spaces. + +When the program runs, you'll first see the layout with no apparent problems: + +![SNAG-0882](https://user-images.githubusercontent.com/46163555/88704649-60954800-d0dc-11ea-885a-1ebadba039b7.jpg) + +Clicking the OK button will cause the program to return from `window.read()` and thus hit our bad key. The result will be a popup window that resembles this: + +![SNAG-0883](https://user-images.githubusercontent.com/46163555/88704635-5bd09400-d0dc-11ea-88a2-42e7386b076b.jpg) + +Note a few things about this error popup. Your shown your bad key and you're also shown what you likely meant. Additionally, you're shown the filename, the line number and the line of code itself that has the error. + +Because this error was recoverable, the program continues to run after you close the error popup. The result is what you expect from this program... the output field is the same as your information input. + +![SNAG-0884](https://user-images.githubusercontent.com/46163555/88704691-71de5480-d0dc-11ea-8800-9379044a3f1f.jpg) + +### Example 2 - Tuple error + +Keys can be a variety of types, including tuples. In this particular program we have a tuple specified in the layout and have used an incorrect tuple in the lookup. Once again the recovery process worked and the program continued. + +![SNAG-0885](https://user-images.githubusercontent.com/46163555/88705188-2d9f8400-d0dd-11ea-9a91-f92cef9f6219.jpg) + +### Example 3 - No close match found + +In this example, as you can see in the error popup, there was such a mismatch that no substitution could be performed. + +![SNAG-0886](https://user-images.githubusercontent.com/46163555/88705707-e6fe5980-d0dd-11ea-8fcc-bc024298705d.jpg) + +This is an unrecoverable error, so a key error exception is raised. + +```python + +Traceback (most recent call last): + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 25, in + func() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 23, in func + main() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 17, in main + window[(1,2,3)].update(values['-IN-']) + File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 8591, in __getitem__ + return self.FindElement(key) + File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 7709, in FindElement + raise KeyError(key) +KeyError: (1, 2, 3) +``` + +If you're running an IDE such as PyCharm, you can use the information from the assert to jump to the line of code in your IDE based on the crash data provided. + +### Choose Your Desired Combination + +There are enough controls on this error handling that you can control how you want your program to fail. If you don't want any popups, and no guessing and would instead like to simply get an exception when the key error happens, then call `set_options` with this combination: + +```python + sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=True, suppress_key_guessing=True) +``` + +This will cause Example #1 above to immediately get an exception when hitting the statement with the error. Even though the guessing is turned off, you're still provided with the closest match to help with your debugging.... + +``` +** Error looking up your element using the key: -O U T The closest matching key: -OUT- +Traceback (most recent call last): + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 25, in + func() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 23, in func + main() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 17, in main + window['-O U T'].update(values['-IN-']) + File "C:\Python\PycharmProjects\GooeyGUI\PySimpleGUI.py", line 8591, in __getitem__ + return self.FindElement(key) + File "C:\Python\PycharmProjects\GooeyGUI\PySimpleGUI.py", line 7709, in FindElement + raise KeyError(key) +KeyError: '-O U T' +``` + ## Common Element Parameters Some parameters that you will see on almost all Element creation calls include: diff --git a/readme.md b/readme.md index de8794aa..803e4a34 100644 --- a/readme.md +++ b/readme.md @@ -1669,7 +1669,7 @@ Parameter Descriptions: | bool | keep_on_top | If True the window will remain above all current windows | | Tuple[int, int] | location | (x,y) Location on screen to display the upper left corner of window | | str) or (bytes | image | Image to include at the top of the popup window | -| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = False | +| bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = True | | Union[str, None] | **RETURN** | Text entered or None if window was closed or cancel button clicked ```python @@ -2588,15 +2588,91 @@ You have a couple of options for dealing this with. If your operation can be br If, on the other hand, your operation is not under your control or you are unable to add `Refresh` calls, then the next option available to you is to move your long operations into a thread. +### The "Old Way" + There are a couple of demo programs available for you to see how to do this. You basically put your work into a thread. When the thread is completed, it tells the GUI by sending a message through a queue. The event loop will run with a timer set to a value that represents how "responsive" you want your GUI to be to the work completing. -These 2 demo programs are called -```python -Demo_Threaded_Work.py - Best documented. Single thread used for long task -Demo_Multithreaded_Long_Tasks.py - Similar to above, but with less fancy GUI. Allows you to set amount of time -``` +### The "New Way" - `Window.write_event_value` -These 2 particular demos have a LOT of comments showing you where to add your code, etc.. The amount of code to do this is actually quite small and you don't need to understand the mechanisms used if you simply follow the demo that's been prepared for you. +This new function that is available currently only in the tkinter port as of July 2020 is exciting and represents the future way multi-threading will be handled in PySimpleGUI (or so is hoped). + +Previously, a queue was used where your event loop would **poll** for incoming messages from a thread. + +Now, threads can directly inject events into a Window so that it will show up in the `window.read()` calls. This allows a your event loop to "pend", waiting for normal window events as well as events being generated by threads. + +You can see this new capability in action in this demo: Demo_Multithreaded_Write_Event_Value.py + +Here is that program for your inspection and education. It's SO nice to no longer poll for threaded events. + +```python +import threading +import time +import PySimpleGUI as sg + +""" + Threaded Demo - Uses Window.write_event_value communications + + Requires PySimpleGUI.py version 4.25.0 and later + + This is a really important demo to understand if you're going to be using multithreading in PySimpleGUI. + + Older mechanisms for multi-threading in PySimpleGUI relied on polling of a queue. The management of a communications + queue is now performed internally to PySimpleGUI. + + The importance of using the new window.write_event_value call cannot be emphasized enough. It will hav a HUGE impact, in + a positive way, on your code to move to this mechanism as your code will simply "pend" waiting for an event rather than polling. + + Copyright 2020 PySimpleGUI.org +""" + +THREAD_EVENT = '-THREAD-' + +cp = sg.cprint + +def the_thread(window): + """ + The thread that communicates with the application through the window's events. + + Once a second wakes and sends a new event and associated value to the window + """ + i = 0 + while True: + time.sleep(1) + window.write_event_value('-THREAD-', (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter + cp('This is cheating from the thread', c='white on green') + i += 1 + +def main(): + """ + The demo will display in the multiline info about the event and values dictionary as it is being + returned from window.read() + Every time "Start" is clicked a new thread is started + Try clicking "Dummy" to see that the window is active while the thread stuff is happening in the background + """ + + layout = [ [sg.Text('Output Area - cprint\'s route to here', font='Any 15')], + [sg.Multiline(size=(65,20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)], + [sg.T('Input so you can see data in your dictionary')], + [sg.Input(key='-IN-', size=(30,1))], + [sg.B('Start A Thread'), sg.B('Dummy'), sg.Button('Exit')] ] + + window = sg.Window('Window Title', layout, finalize=True) + + while True: # Event Loop + event, values = window.read() + cp(event, values) + if event == sg.WIN_CLOSED or event == 'Exit': + break + if event.startswith('Start'): + threading.Thread(target=the_thread, args=(window,), daemon=True).start() + if event == THREAD_EVENT: + cp(f'Data from the thread ', colors='white on purple', end='') + cp(f'{values[THREAD_EVENT]}', colors='white on red') + window.close() + +if __name__ == '__main__': + main() +``` ### Multithreaded Programs @@ -3360,6 +3436,150 @@ Then to turn off return values for that element, the `Multiline` element would b sg.Multiline(size=(40,8), key='-MLINE-' + sg.WRITE_ONLY_KEY) ``` +## Key Errors - Key error recovery algorithm + +In the primary (tkinter) port of PySimpleGUI, starting in version 4.27.0 (not yet on PyPI... but available on GitHub as 4.26.0.14+) + +There are now 3 controls over key error handling and a whole new era of key reporting. + +```python +SUPPRESS_ERROR_POPUPS = False +SUPPRESS_RAISE_KEY_ERRORS = False +SUPPRESS_KEY_GUESSING = False +``` + +You can modify these values by calling `set_options`. + +```python + sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=False, suppress_key_guessing=False) +``` + +A basic definition of them are: +`suppress_error_popups` - Disables error popups that are generated within PySimpleGUI itself to not be shown +`suppress_raise_key_errors` - Disables raising a key error if a key or a close match are not found +`suppress_key_guessing` - Disables the key guessing algorithm should you have a key error + +With the defaults left as defined (all `False`), here is how key errors work. + +This is the program being used in this example: + +```python +import PySimpleGUI as sg + +def main(): + sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=False, suppress_key_guessing=False) + + layout = [ [sg.Text('My Window')], + [sg.Input(k='-IN-'), sg.Text(size=(12,1), key='-OUT-')], + [sg.Button('Go'), sg.Button('Exit')] ] + + window = sg.Window('Window Title', layout, finalize=True) + + while True: # Event Loop + event, values = window.read() + print(event, values) + if event == sg.WIN_CLOSED or event == 'Exit': + break + window['-O U T'].update(values['-IN-']) + window.close() + +def func(): + + main() + +func() +``` + +A few things to note about it: + +* There are multiple levels of functions being called, not just a flat program +* There are 2 keys explicitly defined, both are text at this point (we'll change them later) +* There are 2 lookups happening, one with `window` the other with `values` + +This key error recovery algorithm only applies to element keys being used to lookup keys inside of windows. The `values` key lookup is a plain dictionary and so nothing fancy is done for that lookup. + +### Example 1 - Simple text string misspelling + +In our example, this line of code has an error: + +```python +window['-O U T'].update(values['-IN-']) +``` + +There are multiple problems with the key `'-OUT-'`. It is missing a dash and it has a bunch of extra spaces. + +When the program runs, you'll first see the layout with no apparent problems: + +![SNAG-0882](https://user-images.githubusercontent.com/46163555/88704649-60954800-d0dc-11ea-885a-1ebadba039b7.jpg) + +Clicking the OK button will cause the program to return from `window.read()` and thus hit our bad key. The result will be a popup window that resembles this: + +![SNAG-0883](https://user-images.githubusercontent.com/46163555/88704635-5bd09400-d0dc-11ea-88a2-42e7386b076b.jpg) + +Note a few things about this error popup. Your shown your bad key and you're also shown what you likely meant. Additionally, you're shown the filename, the line number and the line of code itself that has the error. + +Because this error was recoverable, the program continues to run after you close the error popup. The result is what you expect from this program... the output field is the same as your information input. + +![SNAG-0884](https://user-images.githubusercontent.com/46163555/88704691-71de5480-d0dc-11ea-8800-9379044a3f1f.jpg) + +### Example 2 - Tuple error + +Keys can be a variety of types, including tuples. In this particular program we have a tuple specified in the layout and have used an incorrect tuple in the lookup. Once again the recovery process worked and the program continued. + +![SNAG-0885](https://user-images.githubusercontent.com/46163555/88705188-2d9f8400-d0dd-11ea-9a91-f92cef9f6219.jpg) + +### Example 3 - No close match found + +In this example, as you can see in the error popup, there was such a mismatch that no substitution could be performed. + +![SNAG-0886](https://user-images.githubusercontent.com/46163555/88705707-e6fe5980-d0dd-11ea-8fcc-bc024298705d.jpg) + +This is an unrecoverable error, so a key error exception is raised. + +```python + +Traceback (most recent call last): + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 25, in + func() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 23, in func + main() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 17, in main + window[(1,2,3)].update(values['-IN-']) + File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 8591, in __getitem__ + return self.FindElement(key) + File "C:\Python\PycharmProjects\PSG\PySimpleGUI.py", line 7709, in FindElement + raise KeyError(key) +KeyError: (1, 2, 3) +``` + +If you're running an IDE such as PyCharm, you can use the information from the assert to jump to the line of code in your IDE based on the crash data provided. + +### Choose Your Desired Combination + +There are enough controls on this error handling that you can control how you want your program to fail. If you don't want any popups, and no guessing and would instead like to simply get an exception when the key error happens, then call `set_options` with this combination: + +```python + sg.set_options(suppress_raise_key_errors=False, suppress_error_popups=True, suppress_key_guessing=True) +``` + +This will cause Example #1 above to immediately get an exception when hitting the statement with the error. Even though the guessing is turned off, you're still provided with the closest match to help with your debugging.... + +``` +** Error looking up your element using the key: -O U T The closest matching key: -OUT- +Traceback (most recent call last): + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 25, in + func() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 23, in func + main() + File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_978 - key error example.py", line 17, in main + window['-O U T'].update(values['-IN-']) + File "C:\Python\PycharmProjects\GooeyGUI\PySimpleGUI.py", line 8591, in __getitem__ + return self.FindElement(key) + File "C:\Python\PycharmProjects\GooeyGUI\PySimpleGUI.py", line 7709, in FindElement + raise KeyError(key) +KeyError: '-O U T' +``` + ## Common Element Parameters Some parameters that you will see on almost all Element creation calls include: