Metadata property added to call ref. Checkin of the latest readme_creator files

This commit is contained in:
PySimpleGUI 2021-01-17 13:04:15 -05:00
parent faa701e3de
commit fcb3cc7bde
17 changed files with 19766 additions and 8351 deletions

View file

@ -145,7 +145,7 @@ Preview of popups:
<img src="https://user-images.githubusercontent.com/13696193/44957595-9e15da00-aea1-11e8-8909-6b6121b74509.jpg">
</p>
<!-- <+func.Popup+> -->
<!-- <+func.popup+> -->
The other output Popups are variations on parameters. Usually the button_type parameter is the primary one changed.
@ -996,15 +996,96 @@ 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
### The "New Way" - `Window.write_event_value`
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
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
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()
```
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.
### Multithreaded Programs
@ -1129,7 +1210,26 @@ theme_previewer
The first step is to create the window object using the desired window customizations.
Note - There is no direct support for "**modal windows**" in PySimpleGUI. All windows are accessible at all times unless you manually change the windows' settings.
## Modal Windows (only applied to tkinter port currently
)
NOTE - as of PySimpleGUI 4.25.0 Modal Windows are supported! By default the `popup` windows that block will be marked Modal by default. This is a somewhat risky change because your expisting applications will behave differently. However, in theory, you shouldn't have been interacting with other windows while the popup is active. All of those actions are at best queued. It's implementation dependent.
"Modal" in this case means that you must close this "modal" window before you will be able to interact with windows created before this window. Think about an "about" box. You normally have to close this little popup in most programs. So, that's what PySimpleGUI is doing now.
## Making your window modal
To make a Modal Wio=ndow you have 2 options.
1. Set the `moodel=True` parameter in your Window calls.
2. Call the method `Window.make_modal()` to chance a window from non-modal to modal. There is no modal to non-modal. Don't see the need for one. If one comes up, sure!
### Disabling modal windows
Popups that block are the only windows that have modal on by default. There is a modal parameter than you can set to False to turn it off.
For the earlier than 4.25.0 and other ports of PySimpleGUI There is no direct support for "**modal windows**" in PySimpleGUI. All windows are accessible at all times unless you manually change the windows' settings.
**IMPORTANT** - Many of the `Window` methods require you to either call `Window.read` or `Window.Finalize` (or set `finalize=True` in your `Window` call) before you call the method. This is because these 2 calls are what actually creates the window using the underlying GUI Framework. Prior to one of those calls, the methods are likely to crash as they will not yet have their underlying widgets created.
@ -1773,6 +1873,160 @@ 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 <module>
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 <module>
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\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: '-O U T'
```
## Common Element Parameters
@ -2660,7 +2914,6 @@ For example, this layout has a single slider element that spans several rows fol
Without a Column Element you can't create a layout like this. But with it, you should be able to closely match any layout created using tkinter only.
```python
import PySimpleGUI as sg
@ -2695,9 +2948,10 @@ sg.Popup(event, values, line_width=200)
```
### Column, Frame, Tab, Window element_justification
Beginning in Release 4.3 you can set the justification for any container element. This is done through the `element_justification` parameter. This will greatly help anyone that wants to center all of their content in a window. Previously it was difficult to do these kinds of layouts, if not impossible.
## Columns As a Way to Modify Elements
Sometimes Columns are used to contain a single elemnet, but to give that elemously it was difficult to do these kinds of layouts, if not impossible.
justify the `Column` element's row by setting the `Column`'s `justification` parameter.
@ -3675,6 +3929,47 @@ You'll find the pattern - `window.Element(key)` in older code. All of code afte
Note that to change a progress meter's progress, you call `update_bar`, not `update`. A change to this is being considered for a future release.
# Cursors - Setting for Elements and Windows
It is possible to change the normal arrow cursor into something else by setting the cursor for an element or the entire window. The result will be the cursor changing when you move the mouse over the elements or Window.
One of the best examples is URLs. Users are accustomed to seeing a hand cursor when the mouse is moved over a link. By setting the cursor to a hand for a Text element that has text that is in the format of a URL, it signals to the user that it's a link that can be clicked.
The `set_cursor` method is used to set the cursor for an element. Perform an element look-up or use a variable containing an element, and call the `set_cursor` method, passing in a string that selects the cursor. The valid cursor names are documented in the tkinter docs as this call maps directly to a tkinter call.
These cursor strings were obtained from the Tk manual and are what you pass into the `set_cursor` methods.
## Windows Level Cursor
You can also set the cursor for the Window as a whole, including the margins and areas elements don't directly fill. Call `Window.set_cursor()` to set the cursor at the Window level.
## Valid Cursor Strings
`X_cursor, arrow, based_arrow_down, based_arrow_up, boat, bogosity, bottom_left_corner, bottom_right_corner, bottom_side, bottom_tee, box_spiral, center_ptr, circle, clock, coffee_mug, cross, cross_reverse, crosshair, diamond_cross, dot, dotbox, double_arrow, draft_large, draft_small, draped_box, exchange, fleur, gobbler, gumby, hand1, hand2, heart, icon, iron_cross, left_ptr, left_side, left_tee, leftbutton, ll_angle, lr_angle, man, middlebutton, mouse, pencil, pirate, plus, question_arrow, right_ptr, right_side, right_tee, rightbutton, rtl_logo, sailboat, sb_down_arrow, sb_h_double_arrow, sb_left_arrow, sb_right_arrow, sb_up_arrow, sb_v_double_arrow, shuttle, sizing, spider, spraycan, star, target, tcross, top_left_arrow, top_left_corner, top_right_corner, top_side, top_tee, trek, ul_angle, umbrella, ur_angle, watch, xterm`
## No Cursor
To specify no cursor should be shown, the cursor `'no'` can be used on some platforms
## Windows OS Specific
One windows, these cursors map to native Windows cursors:
`arrow, center_ptr, crosshair, fleur, ibeam, icon, sb_h_double_arrow, sb_v_double_arrow, watch, xterm`
And these are also available:
`no, starting, size, size_ne_sw, size_ns, size_nw_se, size_we, uparrow, wait`
## Mac OS Specific
`arrow, cross, crosshair, ibeam, plus, watch, xterm`
And these additional native cursors are available for the Mac
`copyarrow, aliasarrow, contextualmenuarrow, text, cross-hair, closedhand, openhand, pointinghand, resizeleft, resizeright, resizeleftright, resizeup, resizedown, resizeupdown, none, notallowed, poof, countinguphand, countingdownhand, countingupanddownhand, spinning`
# Keyboard & Mouse Capture
@ -4203,6 +4498,400 @@ Exception module 'tkinter' has no attribute '__version__'
```
---
# User Settings API
In release 4.30.0 there is a new set of API calls available to help with "user settings". Think of user settings as a dictionary that is automatically written to your hard drive. That's basically what it is. Underpinning the code is the JSON package provided by Python.
While using JSON files to save and load a settings dictionary isn't very difficult, it is still code you'll need to write if you want to save settings as part of your GUI. Since having "settings" for a GUI based program isn't uncommon, it made sense to build this capability into PySimpleGUI. Clearly you can still use your own method for saving settings, but if you're looking for a simple and easy way to do it, these calls are likely about as easy as it gets.
There have already been some demo programs written that use JSON files to store settings. You can expect that this capability will begin to show up in more demos in the future since it's now part of PySimpleGUI.
User settings are stored in a Python dictionary which is saved to / loaded from disk. Individual settings are thus keys into a dictionary. You do not need to explicitly read nor write the file. Changing any entry will cause the file to be saved. Reading any entry will cause the file to be read if it hasn't already been read.
## Two Interfaces
There are 2 ways to access User Settings
1. User Settings function calls
2. The `UserSettings` class
They both offer the same basic operations. The class interface has an added benefit of being able to access the individual settings using the same syntax as Python dictionary.
## List of Calls for Function Interface
|Function|Description|
| --- | --- |
|user_settings|Returns settings as a dictionary|
|user_settings_delete_entry|Deletes a setting|
|user_settings_delete_filename|Deletes the settings file|
|user_settings_file_exists|Returns True if settings file specified exists|
|user_settings_filename|Returns full path and filename of current settings file|
|user_settings_get_entry|Returns value for a setting. If no setting found, then specified default value is returned|
|user_settings_load|Loads dictionary from the settings file. This is not normally needed||
|user_settings_save|Saves settings to current or newly specified file. Not normally needed|
|user_settings_set_entry|Sets an entry to a particular value
|user_settings_write_new_dictionary|Writes a specified dictionary to settings file|
## Operations
There are 2 categories that the calls can be divided into.
1. File operations
2. Settings operations
File operations involve working with the JSON file itself. They include:
* Setting the path and/or filename
* Load/save the file (these are somewhat optional as the saving loading/saving is done automatically)
* Deleting the settings file
* Checking if settings file exists
Generally speaking, a setting is specified with a key which is generally a string. Settings operations are for working with the individual settings and include:
* Get the value of a setting (returns a default value if not found)
* Set the value of a setting (also saves the settings to disk)
Any setting operation may cause the file to be written. This is because a "get" operation can include returning a default value if the setting isn't found. This means a new entry is made in your settings dictionary is one didn't exist before. Since a new entry is made, that means it needs to be also be written to disk.
## Filenames
The settings filename defaults the filename of your Python file making the call with the extension ".json" added. If your Python program is called `test.py` then your default settings filename will be `test.json`.
In addition to the filename having a default value, the path to the file also has a default value. The default depends on your operating system.
|Operating System|Default Path|
| --- | --- |
| Windows | \user\user_name\AppData\Local\PySimpleGUI\settings |
| Linux | ~/.config/PySimpleGUI/settings |
| Mac | ~/Library/Application Support/PySimpleGUI/settings |
When calling the User Settings APIs, if a parameter is named `filename`, you can specify a full path or just the filename. This will save you the trouble of having to split up your path and filename in your code. If you specify only the path, the the filename will be added to that path and named as defined earlier.
Like the rest of PySimpleGUI, the idea is for you to write as little code as possible. The default values for the filename and path should be fine for you to use. They will be stored in a location on your system that is meant to store user settings.
### Setting Filename
If you want to see what the current filename is for your settings, then you can call `user_settings_filename()` with no parameters and you'll get back an absolute path and filename.
To make the code for specifying the folder and filename as simple as possible, the 2 parts are separated in the call specifying the name of the settings file. However, it is possible to supply a full and complete folder + filename as well.
The default filename for your settings file is the name of the file that makes the call to the User Settings API's with the `.py` extension changed to a `.json` extension. If your source file is called `demo.py`, then your settings filename will be `demo.json`.
#### Setting only the filename
If you want to control the name of the file and/or the path to the settings file, then you will use the `user_settings_filename` call. This function takes 2 parameters.
```python
user_settings_filename(filename=None, path=None)
```
If you set only the path, then the filename will default to the value already described. If you set only the filename, then the path will be the default path is dependent on your operating system. See the table above for the locations for each OS.
```python
import PySimpleGUI as sg
sg.user_settings_filename(filename='my_settings.json')
print(sg.user_settings_filename())
```
If you are running on Windows, then the result of running this code will be this printed on the console:
```
C:\Users\your_use_name\AppData\Local\PySimpleGUI\settings\my_settings.json
```
You are not restricted to naming your settings file to an extension of .json. That is simply the default extension that's used by PySimpleGUI. You can use any extension you would like, including no extension.
#### Setting only the path
Maybe you don't care about the settings filename itself, but you do care about where the settings are stored. Let's say you want the settings to be stored in the same folder as your Python source file. Specifying `path='.'` will achieve this.
#### Setting a fully qualified filename
If you want to specify the full absolute path and filename of the settings file, you can do it by using the filename parameter. Instead of passing the filename only, pass in a fully qualified path and filename. If you want to name your settings file `a:\temp\my_settings`, then your call will look like this:
```python
sg.user_settings_filename(filename=r'a:\temp\my_settings')
```
You are not required to break your file down into 2 parameters. You could if you wanted to however. The equivalent to the above call using 2 parameters would be:
```python
sg.user_settings_filename(filename='my_settings' , path=r'a:\temp')
```
### Getting the current filename
Calling `user_settings_filename` with no parameters will return the full path and filename of your settings file as a single string.
### File Loading / Saving
Generally speaking you will not need to load or save your settings file. It is automatically saved after every change.
Note that reading a setting can also cause the file to be written. If you read a setting and the setting did not exist, then your call to `user_settings_get_entry` will return the default value you specified. As a result, the dictionary is updated with this default value and in return the file is written with this value as well.
One of the situations where you may want to explicitly read/load the settings file is if you're expecting it to be modified by another program.
Like so much of PySimpleGUI, as much as possible is automatically done on your behalf. This includes the requirement of saving and loading your settings file. Even naming your settings file is optional.
## The `UserSettings` Class Interface
The `UserSettings` class makes working with settings look like a Python dictionary. The familiar [ ] syntax is used to read, write and delete entries.
### Creating a `UserSettings` Object
The first step is to create your setting object. The parameters are the same as calling the `user_settings_filename` function. If you want to use the default values, then leave the parameters unchanged.
```python
settings = sg.UserSettings()
```
This is the same as calling `sg.user_settings_filename()`
### Reading, Writing, and Deleting an Individual Settings Using [ ] Syntax
The first operation will be to create the User Settings object.
```python
settings = sg.UserSettings()
```
To read a setting the dictionary-style [ ] syntax is used. If the item's name is `'-item-'`, then reading the value is achieved by writing
```python
item_value = settings['-item-']
```
Writing the setting is the same syntax except the expression is reversed.
```python
settings['-item-'] = new_value
```
To delete an item, again the dictionary style syntax is used.
```python
del settings['-item-']
```
You can also call the delete_entry method to delete the entry.
```python
settings.delete_entry('-item-')
```
### `UserSettings` Methods
You'll find all of the `UserSettings` methods available to you detailed in the Call Reference documentation.
One operation in particular that is not achievable using the [ ] notation is a "get" operation with a default value. For dictionaries, this method is `get` and for the `UserSettings` class the method is also called `get`. They both have an optional second parameter that represents a "default value" should the key not be found in the dictionary.
If you would like a setting with key `'-item-'` to return an empty string `''` instead of `None` if they key isn't found, then you can use this code to achieve that:
```python
value = settings.get('-item-', '')
```
It's the same kind of syntax that you're used to using with dictionaries.
### Default Value
Normally the default value will be `None` if a key is not found and you get the value of the entry using the bracket format:
```python
item_value = settings['-item-']
```
You can change the default value by calling `settings.set_default_value(new_default)`. This will set the default value to return in the case when no key is found. Note that an exception is not raised when there is a key error (see next section on error handling). Instead, the default value is returned with a warning displayed.
## Displaying the Settings Dictionary
The class interface makes it easy to dump out the dictionary. If you print the UserSettings object you'll get a printout of the dictionary.
Note that you'll need to "load" the settings from disk if you haven't performed any operations on the settings.
```python
settings = sg.UserSettings()
settings.load()
print(settings)
```
If you were to print the dictionary after creating the object, then the `load` is not needed
```python
settings = sg.UserSettings()
print(settings['-item-'])
print(settings)
```
To print the dictionary using the function call interface:
```python
print(sg.user_settings())
```
## Error Handling for User Settings
From a GUI perspective, user settings are not critical to the GUI operations itself. There is nothing about settings that will cause your window to not function. As a result, errors that occur in the User Settings are "soft errors". An error message is displayed along with information about how you called the function, when possible, and then execution continues.
One reason for treating these as soft errors and thus not raising an exception is that raising an exception will crash your GUI. If you have redirected your output, which many GUIs do, then you will see no error information and your window will simply disappear. If you double clicked a .py file to launch your GUI, both the GUI and the console window will instantly disappear if the GUI crashes, leaving you no information to help you debug the problem.
The only time errors can occur are during file operations. Typically these errors happen because you've specified a bad path or you don't have write permission for the path you specified.
Example error message. If you executed this code:
```python
def main():
sg.user_settings_filename(path='...')
sg.user_settings_set_entry('-test-',123)
```
Then you'll get an error when trying to set the '-test-' entry because `'...'` is not a valid path.
```
*** Error saving settings to file:***
...\scratch_1065.json [Errno 2] No such file or directory: '...\\scratch_1065.json'
The PySimpleGUI internal reporting function is save
The error originated from:
File "C:/Users/mike/.PyCharmCE2019.1/config/scratches/scratch_1065.py"
line 8
in main
sg.user_settings_set_entry('-test-',123)
```
You should be able to easily figure out these errors as they are file operations and the error messages are clear in detailing what's happened and where the call originated.
### Silenting the Errors
If you're the type that doesn't want to see any error messages printed out on your console, then you can silence the error output.
When using the class interface, there is a parameter `silent_on_error` that you can set to `True`.
For the function interface, call the function `user_settings_silent_on_error()` and set the parameter to `True`
## Coding Convention for User Settings Keys
The User Settings prompted a new coding convention that's been added to PySimpleGUI examples. As you're likely aware, keys in layouts have the format `'-KEY-`'. For UserSettings, a similar format is used, but instead of the string being in all upper case, the characters are lower case. In the example below, the user setting for "filename" has a User Setting key of `'-filename-'`. Coding conventions are a good thing to have in your projects. You don't have to follow this one of course, but you're urged to create your own for places in your code that it makes sense. You could say that PEP8 is one giant coding convention for the Python language as a whole. You don't have to follow it, but most Python programmers do. We follow it "by convention".
The reason this is done in PySimpleGUI is so that the keys are immediately recognizable. Perhaps your application has dictionaries that you use. If you follow the PySimpleGUI coding convention of Element keys have the format `'-KEY-'` and User Settings keys have the format of `'-key-'`, then you'll immediately understand what a specific key is used for. Your company may have its own coding conventions so follow those if appropriate instead of what you see in the PySimpleGUI examples.
## Example User Settings Usage
One of the primary places settings are likely to be used is for filenames / folder names. How many times have you run the same program and needed to enter the same filename? Even if the name of the file is on your clipboard, it's still a pain in the ass to paste it into the input field every time you run the code. Wouldn't it be so much simpler if your program remembered the last value you entered? Well, that's exactly why this set of APIs was developed.... again it was from laziness that this capability gained life.
If you want your `Input` elements to default to an entry from your settings, then you simply set the first parameter (`default_text`) to the value of a setting from your settings file.
Let's say your layout had this typical file input row:
```python
[sg.Input(key='-IN-'), sg.FileBrowse()]
```
To automatically fill in the `Input` to be the last value entered, use this layout row:
```python
[sg.Input(sg.user_settings_get_entry('-filename-', ''), key='-IN-'), sg.FileBrowse()]
```
When your user clicks OK or closes the window in a way that is in a positive way (instead of cancelling), then add this statement to save the value.
```python
sg.user_settings_set_entry('-filename-', values['-IN-'])
```
Here's an entire program demonstrating this way of using user settings
![image](https://user-images.githubusercontent.com/46163555/96048583-cde78800-0e44-11eb-87fe-c2465e1b6cf8.png)
```python
import PySimpleGUI as sg
layout = [[sg.Text('Enter a filename:')],
[sg.Input(sg.user_settings_get_entry('-filename-', ''), key='-IN-'), sg.FileBrowse()],
[sg.B('Save'), sg.B('Exit Without Saving', key='Exit')]]
window = sg.Window('Filename Example', layout)
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Exit'):
break
elif event == 'Save':
sg.user_settings_set_entry('-filename-', values['-IN-'])
window.close()
```
In 2 lines of code you've just made life for your user so much easier. And, by not specifying a location and name for your file, the settings are stored out of sight / out of mind. If you wanted to have the settings be stored with your program file so that it's more visible, then add this statement before your layout:
```python
sg.user_settings_filename(path='.')
```
## Example Using UserSettings Class with [ ] Syntax
The same example can be written using the `UserSettings` class and the [ ] lookup syntax.
Here's the same program as above.
```python
import PySimpleGUI as sg
settings = sg.UserSettings()
layout = [[sg.Text('Enter a filename:')],
[sg.Input(settings.get('-filename-', ''), key='-IN-'), sg.FileBrowse()],
[sg.B('Save'), sg.B('Exit Without Saving', key='Exit')]]
window = sg.Window('Filename Example', layout)
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Exit'):
break
elif event == 'Save':
settings['-filename-'] = values['-IN-']
window.close()
```
If you were to place these 2 examples in the same file so that one ran after the other, you will find that the same settings file is used and thus the value saved in the first example will be read by the second one.
There was one additional line of code added:
```python
settings.set_default_value('') # Set the default not-found value to ''
```
Strictly speaking, this line isn't needed because the Input Element now takes `None` to be the same as a value of `''`, but to produce identical results I added this line of code.
## Demo Programs
There are a number of demo programs that show how to use UserSettings to create a richer experience for your users by remember the last value input into input elements or by adding a Combobox with a history of previously entered values. These upgrades make for a much easier to use GUI, especially when you find yourself typing in the same values or using the same files/folders.
## Brief Caution - User Settings Stick Around
If you're using the default path, remember that previous runs of your file may have old settings that are still in your settings file. It can get confusing when you've forgotten that you previously wrote a setting. Not seeing the filename can have drawbacks like this.
Also, because the settings automatically save after every update, it can be easy to accidently overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk.
To save your Python dictionary to a settings file, simply call `user_settings_write_new_dictionary(dict)`, passing in your dictionary as the parameter.
-------------------------
# Extending PySimpleGUI