Popup - Display a popup Window with as many parms as you wish to include. This is the GUI equivalent of the "print" statement. It's also great for "pausing" your program's flow until the user can read some error messages. If this popup doesn't have the features you want, then you can easily make your own. Popups can be accomplished in 1 line of code: choice, _ = sg.Window('Continue?', [[sg.T('Do you want to continue?')], [sg.Yes(s=10), sg.No(s=10)]], disable_close=True).read(close=True) ``` popup(args=*<1 or N object>, title = None, button_color = None, background_color = None, text_color = None, button_type = 0, auto_close = False, auto_close_duration = None, custom_text = (None, None), non_blocking = False, icon = None, line_width = None, font = None, no_titlebar = False, grab_anywhere = False, keep_on_top = None, location = (None, None), relative_location = (None, None), any_key_closes = False, image = None, modal = True, button_justification = None, drop_whitespace = True) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | Any | *args | Variable number of your arguments. Load up the call with stuff to see! | | str | title | Optional title for the window. If none provided, the first arg will be used instead. | | (str, str) or str | button_color | Color of the buttons shown (text color, button color) | | str | background_color | Window's background color | | str | text_color | text color | | int | button_type | NOT USER SET! Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). There are many Popup functions and they call Popup, changing this parameter to get the desired effect. | | bool | auto_close | If True the window will automatically close | | int | auto_close_duration | time in seconds to keep window open before closing it automatically | | (str, str) or str | custom_text | A string or pair of strings that contain the text to display on the buttons | | bool | non_blocking | If True then will immediately return from the function without waiting for the user's input. | | str or bytes | icon | icon to display on the window. Same format as a Window call | | int | line_width | Width of lines in characters. Defaults to MESSAGE_BOX_LINE_WIDTH | | str or Tuple[font_name, size, modifiers] | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | no_titlebar | If True will not show the frame around the window and the titlebar across the top | | bool | grab_anywhere | If True can grab anywhere to move the window. If no_titlebar is True, grab_anywhere should likely be enabled too | | (int, int) | location | Location on screen to display the top left corner of window. Defaults to window centered on screen | | (int, int) | relative_location | (x,y) location relative to the default location of the window, in pixels. Normally the window centers. This location is relative to the location the window would be created. Note they can be negative. | | bool | keep_on_top | If True the window will remain above all current windows | | bool | any_key_closes | If True then will turn on return_keyboard_events for the window which will cause window to close as soon as any key is pressed. Normally the return key only will close the window. Default is false. | | 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 = True | | bool | right_justify_buttons | If True then the buttons will be "pushed" to the right side of the Window | | str | button_justification | Speficies if buttons should be left, right or centered. Default is left justified | | bool | drop_whitespace | Controls is whitespace should be removed when wrapping text. Parameter is passed to textwrap.fill. Default is to drop whitespace (so popup remains backward compatible) | | str or None | **RETURN** | Returns text of the button that was pressed. None will be returned if user closed window with X The other output Popups are variations on parameters. Usually the button_type parameter is the primary one changed. The choices for button_type are (you should not specify these yourself however): ``` POPUP_BUTTONS_YES_NO POPUP_BUTTONS_CANCELLED POPUP_BUTTONS_ERROR POPUP_BUTTONS_OK_CANCEL POPUP_BUTTONS_OK POPUP_BUTTONS_NO_BUTTONS ``` **Note that you should not call Popup yourself with different button_types.** Rely on the Popup function named that sets that value for you. For example `popup_yes_no` will set the button type to POPUP_BUTTONS_YES_NO for you. ### Scrolled Output There is a scrolled version of Popups should you have a lot of information to display. Show a scrolled Popup window containing the user's text that was supplied. Use with as many items to print as you want, just like a print statement. ``` popup_scrolled(args=*<1 or N object>, title = None, button_color = None, background_color = None, text_color = None, yes_no = False, no_buttons = False, button_justification = "l", auto_close = False, auto_close_duration = None, size = (None, None), location = (None, None), relative_location = (None, None), non_blocking = False, no_titlebar = False, grab_anywhere = False, keep_on_top = None, font = None, image = None, icon = None, modal = True, no_sizegrip = False) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | Any | *args | Variable number of items to display | | str | title | Title to display in the window. | | (str, str) or str | button_color | button color (foreground, background) | | bool | yes_no | If True, displays Yes and No buttons instead of Ok | | bool | no_buttons | If True, no buttons will be shown. User will have to close using the "X" | | str | button_justification | How buttons should be arranged. l, c, r for Left, Center or Right justified | | bool | auto_close | if True window will close itself | | int or float | auto_close_duration | Older versions only accept int. Time in seconds until window will close | | (int, int) | size | (w,h) w=characters-wide, h=rows-high | | (int, int) | location | Location on the screen to place the upper left corner of the window | | (int, int) | relative_location | (x,y) location relative to the default location of the window, in pixels. Normally the window centers. This location is relative to the location the window would be created. Note they can be negative. | | bool | non_blocking | if True the call will immediately return rather than waiting on user input | | str | background_color | color of background | | str | text_color | color of the text | | bool | no_titlebar | If True no titlebar will be shown | | bool | grab_anywhere | If True, than can grab anywhere to move the window (Default = False) | | bool | keep_on_top | If True the window will remain above all current windows | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str or bytes | image | Image to include at the top of the popup window | | bytes or str | icon | filename or base64 string to be used for the window's icon | | 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 | | bool | no_sizegrip | If True no Sizegrip will be shown when there is no titlebar. It's only shown if there is no titlebar | | str or None or TIMEOUT_KEY | **RETURN** | Returns text of the button that was pressed. None will be returned if user closed window with X Typical usage: ```python sg.popup_scrolled(my_text) ``` ![scrolledtextbox 2](https://user-images.githubusercontent.com/13696193/43667324-712aa0d4-9745-11e8-83a9-a0d0570d0865.jpg) The `popup_scrolled` will auto-fit the window size to the size of the text. Specify `None` in the height field of a `size` parameter to get auto-sized height. This call will create a scrolled box 80 characters wide and a height dependent upon the number of lines of text. `sg.popup_scrolled(my_text, size=(80, None))` Note that the default max number of lines before scrolling happens is set to 50. At 50 lines the scrolling will begin. If `non_blocking` parameter is set, then the call will not blocking waiting for the user to close the window. Execution will immediately return to the user. Handy when you want to dump out debug info without disrupting the program flow. ### Non-Blocking Popups - popup_no_wait and the non_blocking parameter Show Popup window and immediately return (does not block) ``` popup_no_wait(args=*<1 or N object>, title = None, button_type = 0, button_color = None, background_color = None, text_color = None, auto_close = False, auto_close_duration = None, non_blocking = True, icon = None, line_width = None, font = None, no_titlebar = False, grab_anywhere = False, keep_on_top = None, location = (None, None), relative_location = (None, None), image = None, modal = False) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | Any | *args | Variable number of items to display | | str | title | Title to display in the window. | | int | button_type | Determines which pre-defined buttons will be shown (Default value = POPUP_BUTTONS_OK). | | (str, str) or str | button_color | button color (foreground, background) | | str | background_color | color of background | | str | text_color | color of the text | | bool | auto_close | if True window will close itself | | int or float | auto_close_duration | Older versions only accept int. Time in seconds until window will close | | bool | non_blocking | if True the call will immediately return rather than waiting on user input | | bytes or str | icon | filename or base64 string to be used for the window's icon | | int | line_width | Width of lines in characters | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | no_titlebar | If True no titlebar will be shown | | bool | grab_anywhere | If True: can grab anywhere to move the window (Default = False) | | (int, int) | location | Location of upper left corner of the window | | (int, int) | relative_location | (x,y) location relative to the default location of the window, in pixels. Normally the window centers. This location is relative to the location the window would be created. Note they can be negative. | | 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 | | str or None | **RETURN** | Reason for popup closing The `popup` call `popup_no_wait` or `popup_non_blocking` will create a popup window and then immediately return control back to you. You can turn other popup calls into non-blocking popups if they have a `non_blocking` parameter. Setting `non_blocking` to True will cause the function to return immediately rather than waiting for the window to be closed. This function is very handy for when you're **debugging** and want to display something as output but don't want to change the programs's overall timing by blocking. Think of it like a `print` statement. There are no return values on one of these Popups. ### Popup Parameter Combinations So that you don't have to specify a potentially long list common parameters there are a number of popup functions that set combinations of parameters. For example `popup_quick_message` will show a non-blocking popup that autocloses and does not have a titlebar. You could achieve this same end result using the plain `popup` call. ## Popup Input There are Popup calls for single-item inputs. These follow the pattern of `popup_get` followed by the type of item to get. There are 3 of these input Popups to choose from, each with settings enabling customization. - `popup_get_text` - get a single line of text - `popup_get_file` - get a filename - `popup_get_folder` - get a folder name Use these Popups instead of making a custom window to get one data value, call the Popup input function to get the item from the user. If you find the parameters are unable to create the kind of window you are looking for, then it's time for you to create your own window. ### popup_get_text Use this Popup to get a line of text from the user. Display Popup with text entry field. Returns the text entered or None if closed / cancelled ``` popup_get_text(message, title = None, default_text = "", password_char = "", size = (None, None), button_color = None, background_color = None, text_color = None, icon = None, font = None, no_titlebar = False, grab_anywhere = False, keep_on_top = None, location = (None, None), relative_location = (None, None), image = None, history = False, history_setting_filename = None, modal = True) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | str | message | message displayed to user | | str | title | Window title | | str | default_text | default value to put into input area | | str | password_char | character to be shown instead of actually typed characters. WARNING - if history=True then can't hide passwords | | (int, int) | size | (width, height) of the InputText Element | | (str, str) or str | button_color | Color of the button (text, background) | | str | background_color | background color of the entire window | | str | text_color | color of the message text | | bytes or str | icon | filename or base64 string to be used for the window's icon | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | no_titlebar | If True no titlebar will be shown | | bool | grab_anywhere | If True can click and drag anywhere in the window to move the window | | bool | keep_on_top | If True the window will remain above all current windows | | (int, int) | location | (x,y) Location on screen to display the upper left corner of window | | (int, int) | relative_location | (x,y) location relative to the default location of the window, in pixels. Normally the window centers. This location is relative to the location the window would be created. Note they can be negative. | | str or bytes | image | Image to include at the top of the popup window | | bool | history | If True then enable a "history" feature that will display previous entries used. Uses settings filename provided or default if none provided | | str | history_setting_filename | Filename to use for the User Settings. Will store list of previous entries in this settings file | | 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 | | str or None | **RETURN** | Text entered or None if window was closed or cancel button clicked ```python import PySimpleGUI as sg text = sg.popup_get_text('Title', 'Please input something') sg.popup('Results', 'The value returned from PopupGetText', text) ``` ![popupgettext](https://user-images.githubusercontent.com/13696193/44957281-8721b880-ae9e-11e8-98cd-d06369f4187e.jpg) ![popup gettext response](https://user-images.githubusercontent.com/13696193/44957282-8721b880-ae9e-11e8-84ae-dc8bb30504a0.jpg) ### popup_get_file Gets one or more filenames from the user. There are options to configure the type of dialog box to show. Normally an "Open File" dialog box is shown. Display popup window with text entry field and browse button so that a file can be chosen by user. ``` popup_get_file(message, title = None, default_path = "", default_extension = "", save_as = False, multiple_files = False, file_types = (('ALL Files', '*.* *'),), no_window = False, size = (None, None), button_color = None, background_color = None, text_color = None, icon = None, font = None, no_titlebar = False, grab_anywhere = False, keep_on_top = None, location = (None, None), relative_location = (None, None), initial_folder = None, image = None, files_delimiter = ";", modal = True, history = False, show_hidden = True, history_setting_filename = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | str | message | message displayed to user | | str | title | Window title | | str | default_path | path to display to user as starting point (filled into the input field) | | str | default_extension | If no extension entered by user, add this to filename (only used in saveas dialogs) | | bool | save_as | if True, the "save as" dialog is shown which will verify before overwriting | | bool | multiple_files | if True, then allows multiple files to be selected that are returned with ';' between each filename | | Tuple[Tuple[str,str]] | file_types | List of extensions to show using wildcards. All files (the default) = (("ALL Files", "*.* *"),). | | bool | no_window | if True, no PySimpleGUI window will be shown. Instead just the tkinter dialog is shown | | (int, int) | size | (width, height) of the InputText Element or Combo element if using history feature | | (str, str) or str | button_color | Color of the button (text, background) | | str | background_color | background color of the entire window | | str | text_color | color of the text | | bytes or str | icon | filename or base64 string to be used for the window's icon | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | no_titlebar | If True no titlebar will be shown | | bool | grab_anywhere | If True: can grab anywhere to move the window (Default = False) | | bool | keep_on_top | If True the window will remain above all current windows | | (int, int) | location | Location of upper left corner of the window | | (int, int) | relative_location | (x,y) location relative to the default location of the window, in pixels. Normally the window centers. This location is relative to the location the window would be created. Note they can be negative. | | str | initial_folder | location in filesystem to begin browsing | | str or bytes | image | Image to include at the top of the popup window | | str | files_delimiter | String to place between files when multiple files are selected. Normally a ; | | 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 | | bool | history | If True then enable a "history" feature that will display previous entries used. Uses settings filename provided or default if none provided | | bool | show_hidden | If True then enables the checkbox in the system dialog to select hidden files to be shown | | str | history_setting_filename | Filename to use for the User Settings. Will store list of previous entries in this settings file | | str or None | **RETURN** | string representing the file(s) chosen, None if cancelled or window closed with X If configured as an Open File Popup then (save_as is not True) the dialog box will look like this. ![snag-0060](https://user-images.githubusercontent.com/13696193/46761050-9831c680-cca1-11e8-8de9-68b15efe2c46.jpg) If you set the parameter save_As to True, then the dialog box looks like this: ![snag-0061](https://user-images.githubusercontent.com/13696193/46761330-2b6afc00-cca2-11e8-953b-f6b5c5ce57f5.jpg) If you choose a filename that already exists, you'll get a warning popup box asking if it's OK. You can also specify a file that doesn't exist. With an "Open" dialog box you cannot choose a non-existing file. A typical call produces this window. ```python text = sg.popup_get_file('Please enter a file name') sg.popup('Results', 'The value returned from popup_get_file', text) ``` ![popupgetfile](https://user-images.githubusercontent.com/13696193/44957857-2fd31680-aea5-11e8-8eb7-f6b91c202cc8.jpg) ### popup_get_folder The window created to get a folder name looks the same as the get a file name. The difference is in what the browse button does. `popup_get_file` shows an Open File dialog box while `popup_get_folder` shows an Open Folder dialog box. Display popup with text entry field and browse button so that a folder can be chosen. ``` popup_get_folder(message, title = None, default_path = "", no_window = False, size = (None, None), button_color = None, background_color = None, text_color = None, icon = None, font = None, no_titlebar = False, grab_anywhere = False, keep_on_top = None, location = (None, None), relative_location = (None, None), initial_folder = None, image = None, modal = True, history = False, history_setting_filename = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | str | message | message displayed to user | | str | title | Window title | | str | default_path | path to display to user as starting point (filled into the input field) | | bool | no_window | if True, no PySimpleGUI window will be shown. Instead just the tkinter dialog is shown | | (int, int) | size | (width, height) of the InputText Element | | (str, str) or str | button_color | button color (foreground, background) | | str | background_color | color of background | | str | text_color | color of the text | | bytes or str | icon | filename or base64 string to be used for the window's icon | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | no_titlebar | If True no titlebar will be shown | | bool | grab_anywhere | If True: can grab anywhere to move the window (Default = False) | | bool | keep_on_top | If True the window will remain above all current windows | | (int, int) | location | Location of upper left corner of the window | | (int, int) | relative_location | (x,y) location relative to the default location of the window, in pixels. Normally the window centers. This location is relative to the location the window would be created. Note they can be negative. | | str | initial_folder | location in filesystem to begin browsing | | 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 = True | | bool | history | If True then enable a "history" feature that will display previous entries used. Uses settings filename provided or default if none provided | | str | history_setting_filename | Filename to use for the User Settings. Will store list of previous entries in this settings file | | str or None | **RETURN** | string representing the path chosen, None if cancelled or window closed with X This is a typical call ```python text = sg.popup_get_folder('Please enter a folder name') sg.popup('Results', 'The value returned from popup_get_folder', text) ``` ![popupgetfolder](https://user-images.githubusercontent.com/13696193/44957861-45484080-aea5-11e8-926c-cf607a45251c.jpg) ### popup_animated ![ring](https://user-images.githubusercontent.com/13696193/51296743-6ee4ad00-19eb-11e9-91f5-cd8086ad1b50.gif) The animated Popup enables you to easily display a "loading" style animation specified through a GIF file that is either stored in a file or a base64 variable. Show animation one frame at a time. This function has its own internal clocking meaning you can call it at any frequency and the rate the frames of video is shown remains constant. Maybe your frames update every 30 ms but your event loop is running every 10 ms. You don't have to worry about delaying, just call it every time through the loop. ``` popup_animated(image_source, message = None, background_color = None, text_color = None, font = None, no_titlebar = True, grab_anywhere = True, keep_on_top = True, location = (None, None), relative_location = (None, None), alpha_channel = None, time_between_frames = 0, transparent_color = None, title = "", icon = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | str or bytes or None | image_source | Either a filename or a base64 string. Use None to close the window. | | str | message | An optional message to be shown with the animation | | str | background_color | color of background | | str | text_color | color of the text | | str or tuple | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | no_titlebar | If True then the titlebar and window frame will not be shown | | bool | grab_anywhere | If True then you can move the window just clicking anywhere on window, hold and drag | | bool | keep_on_top | If True then Window will remain on top of all other windows currently shownn | | (int, int) | location | (x,y) location on the screen to place the top left corner of your window. Default is to center on screen | | (int, int) | relative_location | (x,y) location relative to the default location of the window, in pixels. Normally the window centers. This location is relative to the location the window would be created. Note they can be negative. | | float | alpha_channel | Window transparency 0 = invisible 1 = completely visible. Values between are see through | | int | time_between_frames | Amount of time in milliseconds between each frame | | str | transparent_color | This color will be completely see-through in your window. Can even click through | | str | title | Title that will be shown on the window | | str or bytes | icon | Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO | | bool | **RETURN** | True if the window updated OK. False if the window was closed ***To close animated popups***, call PopupAnimated with `image_source=None`. This will close all of the currently open PopupAnimated windows. # Progress Meters! We all have loops in our code. 'Isn't it joyful waiting, watching a counter scrolling past in a text window? How about one line of code to get a progress meter, that contains statistics about your code? ``` one_line_progress_meter(title, current_value, max_value, args=*<1 or N object>, key = "OK for 1 meter", orientation = "v", bar_color = (None, None), button_color = None, size = (20, 20), border_width = None, grab_anywhere = False, no_titlebar = False, keep_on_top = None, no_button = False) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| | str | title | text to display in titlebar of window | | int | current_value | current value | | int | max_value | max value of progress meter | | Any | *args | stuff to output as text in the window along with the meter | | str or int or tuple or object | key | Used to differentiate between multiple meters. Used to cancel meter early. Now optional as there is a default value for single meters | | str | orientation | 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') | | (str, str) or str | bar_color | The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background. | | (str, str) or str | button_color | button color (foreground, background) | | (int, int) | size | (w,h) w=characters-wide, h=rows-high (Default value = DEFAULT_PROGRESS_BAR_SIZE) | | int | border_width | width of border around element | | bool | grab_anywhere | If True: can grab anywhere to move the window (Default = False) | | bool | no_titlebar | If True: no titlebar will be shown on the window | | bool | keep_on_top | If True the window will remain above all current windows | | bool | no_button | If True: window will be created without a cancel button | | (bool) | **RETURN** | True if updated successfully. False if user closed the meter with the X or Cancel button Here's the one-line Progress Meter in action! ```python for i in range(1,10000): sg.one_line_progress_meter('My Meter', i+1, 10000, 'key','Optional message') ``` That line of code resulted in this window popping up and updating. ![preogress meter](https://user-images.githubusercontent.com/13696193/43667625-d47da702-9746-11e8-91e6-e5177883abae.jpg) A meter AND fun statistics to watch while your machine grinds away, all for the price of 1 line of code. With a little trickery you can provide a way to break out of your loop using the Progress Meter window. The cancel button results in a `False` return value from `one_line_progress_meter`. It normally returns `True`. ***Be sure and add one to your loop counter*** so that your counter goes from 1 to the max value. If you do not add one, your counter will never hit the max value. Instead it will go from 0 to max-1. # Debug Output (easy_print = Print = eprint) Another call in the 'Easy' families of APIs is `EasyPrint`. As is with other commonly used PySimpleGUI calls, there are other names for the same call. You can use `Print` or `eprint` in addition to `EasyPrint`. They all do the same thing, output to a debug window. If the debug window isn't open, then the first call will open it. No need to do anything but stick an 'sg.Print' call in your code. You can even replace your 'print' calls with calls to EasyPrint by simply sticking the statement ```python print = sg.Print ``` at the top of your code. `Print` is one of the better ones to use as it's easy to remember. It is simply `print` with a capital P. `sg.Print('this will go to the debug window')` ```python import PySimpleGUI as sg for i in range(100): sg.Print(i) ``` ![snap0125](https://user-images.githubusercontent.com/13696193/43114979-a696189e-8ecf-11e8-83c7-473fcf0ccc66.jpg) Or if you didn't want to change your code: ```python import PySimpleGUI as sg print=sg.Print for i in range(100): print(i) ``` Just like the standard print call, `easy_print` supports the `sep` and `end` keyword arguments. Other names that can be used to call `easy_print` include `Print`, `eprint`, If you want to close the window, call the function `easy_print_close`. You can change the size of the debug window using the `set_options` call with the `debug_win_size` parameter. There is an option to tell PySimpleGUI to reroute all of your stdout and stderr output to this window. To do so call easy_print with the parameter `do_not_reroute_stdout` set to `False`. After calling it once with this parameter set to True, all future calls to a normal `print` will go to the debug window. If you close the debug window it will re-open the next time you Print to it. If you wish to close the window using your code, then you can call either `easy_print_close()` or `PrintClose()` ### Printing To Multiline Elements Another technique for outputting information that you would normally print is to use the function `Multiline.print`. You'll find it discussed further into this document. The basic idea is that you can easily modify your normal `print` calls to route your printed information to your window. --- # Custom window API Calls (Your First window) This is the FUN part of the programming of this GUI. In order to really get the most out of the API, you should be using an IDE that supports auto complete or will show you the definition of the function. This will make customizing go smoother. This first section on custom windows is for your typical, blocking, non-persistent window. By this I mean, when you "show" the window, the function will not return until the user has clicked a button or closed the window with an X. Two other types of windows exist. 1. Persistent window - the `Window.read()` method returns and the window continues to be visible. This is good for applications like a chat window or a timer or anything that stays active on the screen for a while. 2. Asynchronous window - the trickiest of the lot. Great care must be exercised. Examples are an MP3 player or status dashboard. Async windows are updated (refreshed) on a periodic basis. You can spot them easily as they will have a `timeout` parameter on the call to read. `event, values = window.read(timeout=100)` It's both not enjoyable nor helpful to immediately jump into tweaking each and every little thing available to you. Make some simple windows. Use the Cookbook and the Demo Programs as a way to learn and as a "starting point". ## The window Designer The good news to newcomers to GUI programming is that PySimpleGUI has a window designer. Better yet, the window designer requires no training, no downloads, and everyone knows how to use it. ![gui0_1](https://user-images.githubusercontent.com/13696193/44159598-e2257400-a085-11e8-9b02-343e72cc75c3.JPG) It's a manual process, but if you follow the instructions, it will take only a minute to do and the result will be a nice looking GUI. The steps you'll take are: 1. Sketch your GUI on paper 2. Divide your GUI up into rows 3. Label each Element with the Element name 4. Write your Python code using the labels as pseudo-code Let's take a couple of examples. **Enter a number**.... Popular beginner programs are often based on a game or logic puzzle that requires the user to enter something, like a number. The "high-low" answer game comes to mind where you try to guess the number based on high or low tips. **Step 1- Sketch the GUI** ![gui1_1](https://user-images.githubusercontent.com/13696193/44160127-6a584900-a087-11e8-8fec-09099a8e16f6.JPG) **Step 2 - Divide into rows** ![gui2_1](https://user-images.githubusercontent.com/13696193/44160128-6a584900-a087-11e8-9973-af866fb94c56.JPG) Step 3 - Label elements ![gui6_1](https://user-images.githubusercontent.com/13696193/44160116-64626800-a087-11e8-8b57-671c0461b508.JPG) Step 4 - Write the code The code we're writing is the layout of the GUI itself. This tutorial only focuses on getting the window code written, not the stuff to display it, get results. We have only 1 element on the first row, some text. Rows are written as a "list of elements", so we'll need [ ] to make a list. Here's the code for row 1 ``` [ sg.Text('Enter a number') ] ``` Row 2 has 1 elements, an input field. ``` [ sg.Input() ] ``` Row 3 has an OK button ``` [ sg.OK() ] ``` Now that we've got the 3 rows defined, they are put into a list that represents the entire window. ``` layout = [ [sg.Text('Enter a Number')], [sg.Input()], [sg.OK()] ] ``` Finally we can put it all together into a program that will display our window. ```python import PySimpleGUI as sg layout = [[sg.Text('Enter a Number')], [sg.Input()], [sg.OK()] ] window = sg.Window('Enter a number example', layout) event, values = window.read() window.close() sg.Popup(event, values[0]) ``` Your call to `read` will normally return a dictionary, but will "look like a list" in how you access it. The first input field will be entry 0, the next one is 1, etc.. Later you'll learn about the `key` parameter which allows you to use your own values to identify elements instead of them being numbered for you. ### Example 2 - Get a filename Let's say you've got a utility you've written that operates on some input file and you're ready to use a GUI to enter than filename rather than the command line. Follow the same steps as the previous example - draw your window on paper, break it up into rows, label the elements. ![gui4_1](https://user-images.githubusercontent.com/13696193/44160132-6a584900-a087-11e8-862f-7d791a67ee5d.JPG) ![gui5_1](https://user-images.githubusercontent.com/13696193/44160133-6af0df80-a087-11e8-9dec-bb4d4c59393d.JPG) Writing the code for this one is just as straightforward. There is one tricky thing, that browse for a file button. Thankfully PySimpleGUI takes care of associating it with the input field next to it. As a result, the code looks almost exactly like the window on the paper. ```python import PySimpleGUI as sg sg.theme('Dark Blue 3') # please make your windows colorful layout = [[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ] window = sg.Window('Get filename example', layout) event, values = window.read() window.close() sg.Popup(event, values[0]) ``` Read on for detailed instructions on the calls that show the window and return your results. # Copy these design patterns! All of your PySimpleGUI programs will utilize one of these 2 design patterns depending on the type of window you're implementing. Beginning in release 4.19.0 the constant WIN_CLOSED replaced using `None` as the event signaling that a window was closed. ## Pattern 1 A - "One-shot Window" - Read a window one time then close it This will be the most common pattern you'll follow if you are not using an "event loop" (not reading the window multiple times). The window is read and closed. The input fields in your window will be returned to you as a dictionary (syntactically it looks just like a list lookup) ```python import PySimpleGUI as sg sg.theme('Dark Blue 3') # please make your windows colorful layout = [[sg.Text('SHA-1 and SHA-256 Hashes for the file')], [sg.InputText(), sg.FileBrowse()], [sg.Submit(), sg.Cancel()]] window = sg.Window('SHA-1 & 256 Hash', layout) event, values = window.read() window.close() source_filename = values[0] # the first input element is values[0] ``` ## Pattern 1 B - "One-shot Window" - Read a window one time then close it (compact format) Same as Pattern 1, but done in a highly compact way. This example uses the `close` parameter in `window.read` to automatically close the window as part of the read operation (new in version 4.16.0). This enables you to write a single line of code that will create, display, gather input and close a window. It's really powerful stuff! ```python import PySimpleGUI as sg sg.theme('Dark Blue 3') # please make your windows colorful event, values = sg.Window('SHA-1 & 256 Hash', [[sg.Text('SHA-1 and SHA-256 Hashes for the file')], [sg.InputText(), sg.FileBrowse()], [sg.Submit(), sg.Cancel()]]).read(close=True) source_filename = values[0] # the first input element is values[0] ``` ## Pattern 2 A - Persistent window (multiple reads using an event loop) Some of the more advanced programs operate with the window remaining visible on the screen. Input values are collected, but rather than closing the window, it is kept visible acting as a way to both output information to the user and gather input data. This code will present a window and will print values until the user clicks the exit button or closes window using an X. ```python import PySimpleGUI as sg sg.theme('Dark Blue 3') # please make your windows colorful layout = [[sg.Text('Persistent window')], [sg.Input()], [sg.Button('Read'), sg.Exit()]] window = sg.Window('Window that stays open', layout) while True: event, values = window.read() if event == sg.WIN_CLOSED or event == 'Exit': break print(event, values) window.close() ``` ## Pattern 2 B - Persistent window (multiple reads using an event loop + updates data in window) This is a slightly more complex, but maybe more realistic version that reads input from the user and displays that input as text in the window. Your program is likely to be doing both of those activities (input and output) so this will give you a big jump-start. Do not worry yet what all of these statements mean. Just copy it so you can begin to play with it, make some changes. Experiment to see how thing work. This example introduces the concept of "keys". Keys are super important in PySimpleGUI as they enable you to identify and work with Elements using names you want to use. Keys can be (almost) ANYTHING, except `None` or a List (a tuple is fine). To access an input element's data that is read in the example below, you will use `values['-IN-']` instead of `values[0]` like before. ```python import PySimpleGUI as sg sg.theme('Dark Blue 3') # please make your windows colorful layout = [[sg.Text('Your typed chars appear here:'), sg.Text(size=(12,1), key='-OUTPUT-')], [sg.Input(key='-IN-')], [sg.Button('Show'), sg.Button('Exit')]] window = sg.Window('Window Title', layout) while True: # Event Loop event, values = window.read() print(event, values) if event == sg.WIN_CLOSED or event == 'Exit': break if event == 'Show': # change the "output" element to be the value of "input" element window['-OUTPUT-'].update(values['-IN-']) window.close() ``` ### Qt Designer There actually is a PySimpleGUI Window Designer that uses Qt's window designer. It's outside the scope of this document however. You'll find the project here: https://github.com/nngogol/PySimpleGUIDesigner I hope to start using it more soon. ## How GUI Programming in Python Should Look? At least for beginners ? While one goal was making it simple to create a GUI another just as important goal was to do it in a Pythonic manner. Whether it achieved these goals is debatable, but it was an attempt just the same. The key to custom windows in PySimpleGUI is to view windows as ROWS of GUI Elements. Each row is specified as a list of these Elements. Put the rows together and you've got a window. This means the GUI is defined as a series of Lists, a Pythonic way of looking at things. ### Let's dissect this little program ```python import PySimpleGUI as sg sg.theme('Dark Blue 3') # please make your windows colorful layout = [[sg.Text('Rename files or folders')], [sg.Text('Source for Folders', size=(15, 1)), sg.InputText(), sg.FolderBrowse()], [sg.Text('Source for Files ', size=(15, 1)), sg.InputText(), sg.FileBrowse()], [sg.Submit(), sg.Cancel()]] window = sg.Window('Rename Files or Folders', layout) event, values = window.read() window.close() folder_path, file_path = values[0], values[1] # get the data from the values dictionary print(folder_path, file_path) ``` ### Themes ![image](https://user-images.githubusercontent.com/46163555/70470775-cd01ff00-1a99-11ea-8b9c-8b33c8880c99.png) The first line of code after the import is a call to `theme`. Until Dec 2019 the way a "theme" was specific in PySimpleGUI was to call `change_look_and_feel`. That call has been replaced by the more simple function `theme`. ### Window contents (The Layout) Let's agree the window has 4 rows. The first row only has **text** that reads `Rename files or folders` The second row has 3 elements in it. First the **text** `Source for Folders`, then an **input** field, then a **browse** button. Now let's look at how those 2 rows and the other two row from Python code: ```python layout = [[sg.Text('Rename files or folders')], [sg.Text('Source for Folders', size=(15, 1)), sg.InputText(), sg.FolderBrowse()], [sg.Text('Source for Files ', size=(15, 1)), sg.InputText(), sg.FileBrowse()], [sg.Submit(), sg.Cancel()]] ``` See how the source code mirrors the layout? You simply make lists for each row, then submit that table to PySimpleGUI to show and get values from. And what about those return values? Most people simply want to show a window, get the input values and do something with them. So why break up the code into button callbacks, etc., when I simply want my window's input values to be given to me. For return values the window is scanned from top to bottom, left to right. Each field that's an input field will occupy a spot in the return values. In our example window, there are 2 fields, so the return values from this window will be a dictionary with 2 values in it. Remember, if you do not specify a `key` when creating an element, one will be created for you. They are ints starting with 0. In this example, we have 2 input elements. They will be addressable as values[0] and values[1] ### "Reading" the window's values (also displays the window) ```python event, values = window.read() folder_path, file_path = values[0], values[1] ``` In one statement we both show the window and read the user's inputs. In the next line of code the *dictionary* of return values is split into individual variables `folder_path` and `file_path`. Isn't this what a Python programmer looking for a GUI wants? Something easy to work with to get the values and move on to the rest of the program, where the real action is taking place. Why write pages of GUI code when the same layout can be achieved with PySimpleGUI in 3 or 4 lines of code. 4 lines or 40? Most would choose 4. ## Return values There are 2 return values from a call to `Window.read()`, an `event` that caused the `Read` to return and `values` a list or dictionary of values. If there are no elements with keys in the layout, then it will be a list. However, some elements, like some buttons, have a key automatically added to them. **It's best to use keys on all of your input type elements.** ### Two Return Values All Window Read calls return 2 values. By convention a read statement is written: ```python event, values = window.read() ``` You don't HAVE to write your reads in this way. You can name your variables however you want. But if you want to code them in a way that other programmers using PySimpleGUI are used to, then use this statement. ## Events The first parameter `event` describes **why** the read completed. Events are one of these: For all Windows: * Button click * Window closed using X For Windows that have specifically enabled these. Please see the appropriate section in this document to learn about how to enable these and what the event return values are. * Keyboard key press * Mouse wheel up/down * Menu item selected * An Element Changed (slider, spinner, etc.) * A list item was clicked * Return key was pressed in input element * Timeout waiting for event * Text was clicked * Combobox item chosen * Table row selected * etc. ***Most*** of the time the event will be a button click or the window was closed. The other Element-specific kinds of events happen when you set `enable_events=True` when you create the Element. ### Button Click Events By default buttons will always return a click event, or in the case of realtime buttons, a button down event. You don't have to do anything to enable button clicks. To disable the events, disable the button using its Update method. You can enable an additional "Button Modified" event by setting `enable_events=True` in the Button call. These events are triggered when something 'writes' to a button, ***usually*** it's because the button is listed as a "target" in another button. The button value from a Read call will be one of 2 values: 1. The Button's text - Default 2. The Button's key - If a key is specified If a button has a key set when it was created, then that key will be returned, regardless of what text is shown on the button. If no key is set, then the button text is returned. If no button was clicked, but the window returned anyway, the event value is the key that caused the event to be generated. For example, if `enable_events` is set on an `Input` Element and someone types a character into that `Input` box, then the event will be the key of the input box. ### Element Events Some elements are capable of generating events when something happens to them. For example, when a slider is moved, or list item clicked on or table row clicked on. These events are not enabled by default. To enable events for an Element, set the parameter `enable_events=True`. This is the same as the older `click_submits` parameter. You will find the `click_submits` parameter still in the function definition. You can continue to use it. They are the same setting. An 'or' of the two values is used. In the future, click_submits will be removed so please migrate your code to using `enable_events`. |Name|events| | --- | --- | | InputText | any change | | Combo | item chosen | | Listbox | selection changed | | Radio | selection changed | | Checkbox | selection changed | | Spinner | new item selected | | Multiline | any change | | Text | clicked | | Status Bar | clicked | | Graph | clicked | | Graph | dragged | | Graph | drag ended (mouse up) | | TabGroup | tab clicked | | Slider | slider moved | | Table | row selected | | Tree | node selected | | ButtonMenu | menu item chosen | | Right click menu | menu item chosen | ### Other Events #### Menubar menu item chosen for MenuBar menus and ButtonMenu menus You will receive the key for the MenuBar and ButtonMenu. Use that key to read the value in the return values dictionary. The value shown will be the full text plus key for the menu item chosen. Remember that you can put keys onto menu items. You will get the text and the key together as you defined it in the menu definition. #### Right Click menu item chosen Unlike menu bar and button menus, you will directly receive the menu item text and its key value. You will not do a dictionary lookup to get the value. It is the event code returned from WindowRead(). #### Windows - keyboard, mouse scroll wheel Windows are capable of returning keyboard events. These are returned as either a single character or a string if it's a special key. Experiment is all I can say. The mouse scroll wheel events are also strings. Put a print in your code to see what's returned. #### Timeouts If you set a timeout parameter in your read, then the system TIMEOUT_KEY will be returned. If you specified your own timeout key in the Read call then that value will be what's returned instead. ## Window Closed Events Detecting and correctly handling Windows being closed is an important part of your PySimpleGUI application. You will find in every event loop in every Demo Program an if statement that checks for the events that signal that a window has closed. The most obvious way to close a window is to click the "X" in the upper right corner of the window (on Windows, Linux.... Mac doesn't use an "X" but still has a close button). On Windows systems, the keyboard keys ALT+F4 will force a Window to close. This is one way to close a window without using a mouse. Some programs can also send a "close" command to the window. Regardless of how the close is performed on the window, PySimpleGUI returns an event for this closure. ### WIN_CLOSED Event **The constant WIN_CLOSED (None) is returned when the user clicks the X to close a window.** Typically, the check for a closed window happens right after the `window.read()` call returns. The reason for this is that operating on a closed window can result in errors. The check for closure is an "if" statement. **ALWAYS** include a check for a closed window in your event loop. ### The Window Closed If Statement There are 2 forms you'll find for this if statement in the documentation and the Demo Programs. One is "Pythonic" the other is more understandable by beginners. This is the format you'll see most often in the PySimpleGUI materials if the window has both a button that is used to signal the user wishes to exit. In this example, I'm using a "Quit" button: ```python if event == sg.WIN_CLOSED or event == 'Exit': break ``` The more "Pythonic" version of this same statement is: ```python if event in (sg.WIN_CLOSED, 'Exit'): break ``` In case you're yelling at your screen right now that the second form should always be used, remember that many of the PySimpleGUI users are new to Python. If the very first example of a PySimpleGUI program they see has that if statement in it, they will instantly be lost before they can even begin their journey. So, the decision was made to, you guessed it, go SIMPLE. The statement with the "or" is simpler to understand. This notion of binary choices in programming that's crept in over the past couple decades... that there's a "BEST" or "only 1 right way"... loses the sophisticated thinking that a software engineer needs to be successful in a wide variety of situations. Every situation, every person, every problem... is unique. **There is no idealized best that always fits.** ### A Complete Example - Traditional Window Closed Check Let's put this if statement into context so you can see where it goes and how it works with the event loop ```python import PySimpleGUI as sg layout = [[sg.Text('Very basic Window')], [sg.Text('Click X in titlebar or the Exit button')], [sg.Button('Go'), sg.Button('Exit')]] window = sg.Window('Window Title', layout) while True: event, values = window.read() print(event, values) if event == sg.WIN_CLOSED or event == 'Exit': break window.close() ``` Notice that the line after the while loop is a call to `window.close()`. The reason for this is that exiting the loop can be in 2 ways. * The "X" is clicked * The Exit button is clicked If the Exit button is clicked, the window will still be open. Get in the habit of closing your windows explicitly like this. If the user clicked "X" and closed the window, then it will have been destroyed by the underlying framework. You should STILL call `window.close()` because come cleanup work may be needed and there is no harm in closing an already closed window. tkinter can generate an error/warning sometimes if you don't close the window. For other ports, such as PySimpleGUIWeb, not closing the Window will potentially cause your program to continue to run in the background. This can cause your program to not be visible and yet consuming 100% of the CPU time. Not fun for your users. ### Window Close Confirmation In 4.33.0 a new parameter, `enable_close_attempted_event`, was added to the `Window` object. This boolean parameter indicates if you would like to receive an event that a user **wants** to close the window rather than an event that the user **has** closed the window. To enable this feature, the `Window` is created with something like this: ```python window = sg.Window('Window Title', layout, enable_close_attempted_event=True) ``` When the close attempted feature is enabled, when the user clicks the "X" or types ALT+F4, you will not get a WIN_CLOSED event like previously, you will instead get an event `WINDOW_CLOSE_ATTEMPTED_EVENT` and the window will remain open. Usually this feature is used to add a "close confirmation" popup. The flow goes something like this: * Window is shown * User clicks X * A popup window is shown with message "Do you really want to close the window?" * If confirmed a close is desired, the window is closed. It not, the event loop continues on, basically ignoring the event occurred. ### A Complete Example - Window Closed Confirmation (`enable_close_attempted_event=True`) Returning to the example used above, there has been only 2 modifications. 1. Added the parameter `enable_close_attempted_event=True` to the call to `Window` 2. The if statement in the event loop has changed to add a confirmation ```python import PySimpleGUI as sg layout = [[sg.Text('Very basic Window')], [sg.Text('Click X in titlebar or the Exit button')], [sg.Button('Go'), sg.Button('Exit')]] window = sg.Window('Window Title', layout, enable_close_attempted_event=True) while True: event, values = window.read() print(event, values) if (event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT or event == 'Exit') and sg.popup_yes_no('Do you really want to exit?') == 'Yes': break window.close() ``` The event loop changed from a check like this: ```python if event == sg.WIN_CLOSED or event == 'Exit': break ``` To one like this: ```python if (event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT or event == 'Exit') and sg.popup_yes_no('Do you really want to exit?') == 'Yes': break ``` Let's run this last program so you can see what all this looks like to users. In both cases that a user previously exited the window, there is now an additional confirmation step. ![download](https://raw.githubusercontent.com/PySimpleGUI/PySimpleGUI/master/images/for_readme/Window%20Closed%20Confirmation.gif) ### Demo Programs.... A PySimpleGUI Program's Best Friend Like all features of PySimpleGUI, one of the best resources available to you to learn about parameters, like this close attempted parameter, are the Demo Programs. When you run into a parameter or a feature you've not used before, one way to find some examples of its use is to use the Demo Browser to search through the demo programs. You'll find the Demo Browser described in the Cookbook. As of this writing, the name of the Demo Program Browser is: `Browser_START_HERE_Demo_Programs_Browser.py` If you enter the parameter described in this section - `enable_close_attempted_event` you'll find a Demo Program that uses this parameter. ![DemoBrowser](https://raw.githubusercontent.com/PySimpleGUI/PySimpleGUI/master/images/for_readme/Demo%20Browser%20Search%20Close%20Attempted.jpg) This demo shows you code similar to the code used in this section of the documentation. Use this Browser program! It will make finding examples ***much easier***! See the section up above on the pip-installable version of the demos (search for - `psgdemos`) to get the super-quick method of getting the demo programs and the Demo Browser. ### The `values` Variable - Return values as a list The second parameter from a Read call is either a list or a dictionary of the input fields on the Window. By default return values are a list of values, one entry for each input field, but for all but the simplest of windows the return values will be a dictionary. This is because you are likely to use a 'key' in your layout. When you do, it forces the return values to be a dictionary. Each of the Elements that are Input Elements will have a value in the list of return values. If you know for sure that the values will be returned as a list, then you could get clever and unpack directly into variables. event, (filename, folder1, folder2, should_overwrite) = sg.Window('My title', window_rows).read() Or, more commonly, you can unpack the return results separately. This is the preferred method because it works for **both** list and dictionary return values. ```python event, values = sg.Window('My title', window_rows).read() event, value_list = window.read() value1 = value_list[0] value2 = value_list[1] ... ``` However, this method isn't good when you have a lot of input fields. If you insert a new element into your window then you will have to shuffle your unpacks down, modifying each of the statements to reference `value_list[x]`. The more common method is to request your values be returned as a dictionary by placing keys on the "important" elements (those that you wish to get values from and want to interact with) ### `values` Variable - Return values as a dictionary For those of you that have not encountered a Python dictionary, don't freak out! Just copy and paste the sample code and modify it. Follow this design pattern and you'll be fine. And you might learn something along the way. For windows longer than 3 or 4 fields you will want to use a dictionary to help you organize your return values. In almost all (if not all) of the demo programs you'll find the return values being passed as a dictionary. It is not a difficult concept to grasp, the syntax is easy to understand, and it makes for very readable code. The most common window read statement you'll encounter looks something like this: `window = sg.Window("My title", layout).read()` To use a dictionary, you will need to: * Mark each input element you wish to be in the dictionary with the keyword `key`. If **any** element in the window has a `key`, then **all** of the return values are returned via a dictionary. If some elements do not have a key, then they are numbered starting at zero. Let's take a look at your first dictionary-based window. ```python import PySimpleGUI as sg sg.theme('Dark Blue 3') # please make your windows colorful layout = [ [sg.Text('Please enter your Name, Address, Phone')], [sg.Text('Name', size=(15, 1)), sg.InputText('1', key='-NAME-')], [sg.Text('Address', size=(15, 1)), sg.InputText('2', key='-ADDRESS-')], [sg.Text('Phone', size=(15, 1)), sg.InputText('3', key='-PHONE-')], [sg.Submit(), sg.Cancel()] ] window = sg.Window('Simple data entry window', layout) event, values = window.read() window.close() sg.Popup(event, values, values['-NAME-'], values['-ADDRESS-'], values['-PHONE-']) ``` To get the value of an input field, you use whatever value used as the `key` value as the index value. Thus to get the value of the name field, it is written as values['-NAME-'] Think of the variable values in the same way as you would a list, however, instead of using 0,1,2, to reference each item in the list, use the values of the key. The Name field in the window above is referenced by `values['-NAME-']`. You will find the key field used quite heavily in most PySimpleGUI windows unless the window is very simple. One convention you'll see in many of the demo programs is keys being named in all caps with an underscores at the beginning and the end. You don't HAVE to do this... your key value may look like this: `key = '-NAME-'` The reason for this naming convention is that when you are scanning the code, these key values jump out at you. You instantly know it's a key. Try scanning the code above and see if those keys pop out. `key = '-NAME-'` ## The Event Loop / Callback Functions All GUIs have one thing in common, an "event loop". Usually the GUI framework runs the event loop for you, but sometimes you want greater control and will run your own event loop. You often hear the term event loop when discussing embedded systems or on a Raspberry Pi. With PySimpleGUI if your window will remain open following button clicks, then your code will have an event loop. If your program shows a single "one-shot" window, collects the data and then has no other GUI interaction, then you don't need an event loop. There's nothing mysterious about event loops... they are loops where you take care of.... wait for it..... *events*. Events are things like button clicks, key strokes, mouse scroll-wheel up/down. This little program has a typical PySimpleGUI Event Loop. The anatomy of a PySimpleGUI event loop is as follows, *generally speaking*. * The actual "loop" part is a `while True` loop * "Read" the event and any input values the window has * Check to see if window was closed or user wishes to exit * A series of `if event ....` statements ## All Elements Here is a complete, short, program that contains *all of the PySimpleGUI Elements*. ```python import PySimpleGUI as sg """ Demo - Element List All 34 elements shown in 1 window as simply as possible. Copyright 2022 PySimpleGUI """ use_custom_titlebar = False def make_window(theme=None): NAME_SIZE = 23 def name(name): dots = NAME_SIZE-len(name)-2 return sg.Text(name + ' ' + '•'*dots, size=(NAME_SIZE,1), justification='r',pad=(0,0), font='Courier 10') sg.theme(theme) treedata = sg.TreeData() treedata.Insert("", '_A_', 'Tree Item 1', [1234], ) treedata.Insert("", '_B_', 'B', []) treedata.Insert("_A_", '_A1_', 'Sub Item 1', ['can', 'be', 'anything'], ) layout_l = [[name('Text'), sg.Text('Text')], [name('Input'), sg.Input(s=15)], [name('Multiline'), sg.Multiline(s=(15,2))], [name('Output'), sg.Output(s=(15,2))], [name('Combo'), sg.Combo(sg.theme_list(), default_value=sg.theme(), s=(15,22), enable_events=True, readonly=True, k='-COMBO-')], [name('OptionMenu'), sg.OptionMenu(['OptionMenu',],s=(15,2))], [name('Checkbox'), sg.Checkbox('Checkbox')], [name('Radio'), sg.Radio('Radio', 1)], [name('Spin'), sg.Spin(['Spin',], s=(15,2))], [name('Button'), sg.Button('Button')], [name('ButtonMenu'), sg.ButtonMenu('ButtonMenu', sg.MENU_RIGHT_CLICK_EDITME_EXIT)], [name('Slider'), sg.Slider((0,10), orientation='h', s=(10,15))], [name('Listbox'), sg.Listbox(['Listbox', 'Listbox 2'], no_scrollbar=True, s=(15,2))], [name('Image'), sg.Image(sg.EMOJI_BASE64_HAPPY_THUMBS_UP)], [name('Graph'), sg.Graph((125, 50), (0,0), (125,50), k='-GRAPH-')] ] layout_r = [[name('Canvas'), sg.Canvas(background_color=sg.theme_button_color()[1], size=(125,50))], [name('ProgressBar'), sg.ProgressBar(100, orientation='h', s=(10,20), k='-PBAR-')], [name('Table'), sg.Table([[1,2,3], [4,5,6]], ['Col 1','Col 2','Col 3'], num_rows=2)], [name('Tree'), sg.Tree(treedata, ['Heading',], num_rows=3)], [name('Horizontal Separator'), sg.HSep()], [name('Vertical Separator'), sg.VSep()], [name('Frame'), sg.Frame('Frame', [[sg.T(s=15)]])], [name('Column'), sg.Column([[sg.T(s=15)]])], [name('Tab, TabGroup'), sg.TabGroup([[sg.Tab('Tab1',[[sg.T(s=(15,2))]]), sg.Tab('Tab2', [[]])]])], [name('Pane'), sg.Pane([sg.Col([[sg.T('Pane 1')]]), sg.Col([[sg.T('Pane 2')]])])], [name('Push'), sg.Push(), sg.T('Pushed over')], [name('VPush'), sg.VPush()], [name('Sizer'), sg.Sizer(1,1)], [name('StatusBar'), sg.StatusBar('StatusBar')], [name('Sizegrip'), sg.Sizegrip()] ] layout = [[sg.MenubarCustom([['File', ['Exit']], ['Edit', ['Edit Me', ]]], k='-CUST MENUBAR-',p=0)] if use_custom_titlebar else [sg.Menu([['File', ['Exit']], ['Edit', ['Edit Me', ]]], k='-CUST MENUBAR-',p=0)], [sg.Checkbox('Use Custom Titlebar & Menubar', use_custom_titlebar, enable_events=True, k='-USE CUSTOM TITLEBAR-')], [sg.T('PySimpleGUI Elements - Use Combo to Change Themes', font='_ 18', justification='c', expand_x=True)], [sg.Col(layout_l), sg.Col(layout_r)]] window = sg.Window('The PySimpleGUI Element List', layout, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, keep_on_top=True, use_custom_titlebar=use_custom_titlebar) window['-PBAR-'].update(30) # Show 30% complete on ProgressBar window['-GRAPH-'].draw_image(data=sg.EMOJI_BASE64_HAPPY_JOY, location=(0,50)) # Draw something in the Graph Element return window # Start of the program... window = make_window() while True: event, values = window.read() sg.popup(event, values) # show the results of the read in a popup Window if event == sg.WIN_CLOSED or event == 'Exit': break if values['-COMBO-'] != sg.theme(): sg.theme(values['-COMBO-']) window.close() window = make_window() if event == '-USE CUSTOM TITLEBAR-': use_custom_titlebar = values['-USE CUSTOM TITLEBAR-'] window.close() window = make_window() window.close() ``` When you run it, assuming you've not set a default theme in the system settings, you'll see a window that looks like this (when on Windows): ![image](https://user-images.githubusercontent.com/46163555/152656799-1b5ec932-d2ec-470d-9df3-42121e6298c9.png) The checkbox at the top that reads "Use Custom Titlebar & Menubar" will enable you to see the what the window looks like using the PySimpleGUI Custom Titlebar and Custom Menubar. ![image](https://user-images.githubusercontent.com/46163555/152656821-5b393d2c-4081-480c-9735-2297d67305d4.png) Using this one test program, you can easily browse the PySimpleGUI Themes by choosing a new color Theme using the Combo Element. ![image](https://user-images.githubusercontent.com/46163555/152656849-6107854a-610a-4c74-a521-923e3394b9cc.png) ### But it's so UGLY! ![image](https://user-images.githubusercontent.com/46163555/152658001-3e270768-0636-48f4-90b7-1fa4bb901ab2.png) On Windows, using straight Python, this is the non-GUI alternative. ![image](https://user-images.githubusercontent.com/46163555/152658061-fb6bbbb4-b298-41a2-a725-4352719906a4.png) The 1970s and 1980s were a great era for computing, but I wouldn't call the user interfaces "attractive". Borland made some great tools in the 90's for doing TUIs. There are some excellent Python packages available for you if you're after a TUI! If that's the look you're after, give them a try. You may like the results. But, it's not what PySimpleGUI is about. We're making GUIs for the masses, something you can give to anyone and they'll understand how to use it. If you asked a Windows user (i.e. a normal person, not a programmer) to open a command window in the past decade then you likely got a worried look back. Normal people aren't going to use your Python program that's command line based and be thrilled about it. Your programming pals will love your TUI, Mary, who works in accounting, will not. The example window may look "ugly" or dated to you. ***It's fair to say that it looks dated***. While PySimpleGUI Themes instantly give you a color scheme that matches, it doesn't go much further in making tkinter more attractive/"modern". ***However***, you are far from being out of options to improve the look of your GUI. This bare-bones program is your **starting point**. This is where you begin the process of making an attractive user interface. You've not yet ***started*** to make your interface look better. No effort has been made to make it look nice. It's purely functional. There are 34 Elements in the window. 34 Elements. Considering what it does, it's miraculous or in the least incredibly impressive. Why? Because in 65 lines of Python code that window was created, shown, and collected the results. Let's be clear here... this window will take a massive amount of code using the conventional Python GUI packages. If you manage to write a tkinter, Qt or WxPython program that shows all of these Widgets in under 65 lines of Python code, then by all means send the code over. Fair, side by side tests, recreating the exact same application, have shown numerous times that PySimpleGUI code is at least 1/2 to 1/10th the amount of code than tkinter requires. There is no magic behind why it's so much shorter. PySimpleGUI is writing all the boilerplate code on your behalf. The same tkinter code is being executed that you would write by hand. The difference is that the code is contained in the PySimpleGUI source code instead of in your application code. That's all that's going on with "wrappers" like PySimpleGUI. Code is being generated and executed for you in order to simplify your code. A large amount of time nor code is required to create custom Buttons and other elements, including entirely new and more complex elements such a Dials or Gauges. Transforming a button from plain to an image is a parameter to the Button element. It's... not... THAT... **HARD**. You're a programmer, write some code, or *add a parameter* if you want more attractive interfaces. The hooks are there waiting for you to be creative. ### Window Closed Events **`Note, event values can be None`**. The value for `event` will be the text that is displayed on the button element when it was created or the key for the button. If the user closed the window using the "X" in the upper right corner of the window, then `event` will be `sg.WIN_CLOSED` which is equal to `None`. It is ***vitally*** ***important*** that your code contain the proper checks for `sg.WIN_CLOSED`. For "persistent windows", **always give your users a way out of the window**. Otherwise you'll end up with windows that never properly close. It's literally 2 lines of code that you'll find in every Demo Program. While you're at it, make sure a `window.close()` call is after your event loop so that your window closes for sure. ### The `values` Dictionary You can see in the results Popup window that the values returned are a dictionary. Each input field in the window generates one item in the return values list. Input fields often return a `string`. Check Boxes and Radio Buttons return `bool`. Sliders return float or perhaps int depending on how you configured it or which port you're using. If your window has no keys and it has no buttons that are "browse" type of buttons, then it will return values to you as a list instead of a dictionary. If possible PySimpleGUI tries to return the values as a list to keep things simple. Note in the list of return values in this example, many of the keys are numbers. That's because no keys were specified on any of the elements (although one was automatically made for you). If you don't specify a key for your element, then a number will be sequentially assigned. For elements that you don't plan on modifying or reading values from, like a Text Element, you can skip adding keys. For other elements, you'll likely want to add keys so that you can easily access the values and perform operations on them. ## Operations That Take a "Long Time" If you're a Windows user you've seen windows show in their title bar "Not Responding" which is soon followed by a Windows popup stating that "Your program has stopped responding". Well, you too can make that message and popup appear if you so wish! All you need to do is execute an operation that takes "too long" (i.e. a few seconds) inside your event loop. You have a couple of options for dealing this with. If your operation can be broken up into smaller parts, then you can call `Window.refresh()` occasionally to avoid this message. If you're running a loop for example, drop that call in with your other work. This will keep the GUI happy and Window's won't complain. 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. ### Threading Made Simple You've been at Python for a couple weeks, bravely ventured into GUI world, and the function you called when a button click is detected takes 20 seconds to complete. Your window displays the "Not Responding" message. If you ask what to do on StackOverflow, you'll be told that "all you have to do is use threads". ![image](https://user-images.githubusercontent.com/46163555/152658001-3e270768-0636-48f4-90b7-1fa4bb901ab2.png) Two weeks into programming, and you've just been told you need to learn Threading? This exact scenario is why the window method `perform_long_operation` was created. It's a way for you to "ease into" threading without learning about threading. Forget that thread was even mentioned. Let's just talk about "long operations" instead. There are 2 ways you can use this call. If your function does not take any parameters, then you can enter the name of the function (**without the parentheses!**) as the first parameter to `perform_long_operation`. ```python window.perform_long_operation(my_function, '-FUNCTION COMPLETED-') ``` If your function has parameters, then you'll need to use a lambda. Don't freak out at hearing "lambda" (yet). Just add it as in this example code... ```python window.perform_long_operation(lambda: my_function_with_parms(10), '-FUNCTION COMPLETED-') ``` Write the first parameter was if you were calling your function, and then put `lambda: ` in front of that. The second parameter to `perform_long_operation` is the event (a key) that you want to get back from `window.read()` when your function returns. Finally, if your function has a return value, then that value will show up in the `values` dictionary with the key that you provided. In this example, `values['-FUNCTION COMPLETED-']` will contain the return value from your function. ```python import PySimpleGUI as sg import time def my_function(): time.sleep(30) def my_function_with_parms(duration): time.sleep(duration) return 'My Return Value' layout = [ [sg.Text('Call a lengthy function')], [sg.Button('Start'), sg.Button('Start 2'), sg.Button('Exit')] ] window = sg.Window('Long Operation Example', layout) while True: event, values = window.read() print(event, values) if event == sg.WIN_CLOSED or event == 'Exit': break if event == 'Start': window.perform_long_operation(my_function, '-FUNCTION COMPLETED-') elif event == 'Start 2': window.perform_long_operation(lambda: my_function_with_parms(10), '-FUNCTION COMPLETED-') elif event == '-FUNCTION COMPLETED-': sg.popup('Your function completed!') window.close() ``` So, in summary, `window.perform_long_operation` starts and manages the thread on your behalf. You are returned an event when your "long operation" completes. If your function returns a value, then that value will show up in the `values` variable from your `window.read()` call. Threading made simple. ### `window.perform_long_operation`Has An Alias `window.start_thread` If you're a threading pro, then maybe you would prefer the alias for `window.perform_long_operation`. In addition to this name for the method, you'll get the exact same results by calling `window.start_thread`. Since threading is becoming more popular due to the `write_event_value` capability, it makes sense to enable you to call a thread a thread if that's what you prefer... so enjoy the `start_thread` alias if that's how you roll. ### Threading 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 is handled in PySimpleGUI. 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. It is much more efficient than polling. You can see this new capability in action in this demo: `Demo_Multithreaded_Write_Event_Value.py` Just use the Demo Browser to search for `write_event_value` to get more examples. 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() ``` You'll find plenty of examples to help you with these concepts if you look in the Demo Programs. So, pip install the Demo Programs and the Demo Browser so that you can easily search and find examples of what you're trying to accomplish. --- # Building Custom Windows You will find it ***much easier*** to write code using PySimpleGUI if you use an IDE such as ***PyCharm***. The features that show you documentation about the API call you are making will help you determine which settings you want to change, if any. In PyCharm, two commands are particularly helpful. Control-Q (when cursor is on function name) brings up a box with the function definition Control-P (when cursor inside function call "()") shows a list of parameters and their default values ## Synchronous / Asynchronous Windows The most common use of PySimpleGUI is to display and collect information from the user. The most straightforward way to do this is using a "blocking" GUI call. Execution is "blocked" while waiting for the user to close the GUI window/dialog box. You've already seen a number of examples above that use blocking windows. You'll know it blocks if the `Read` call has no timeout parameter. A blocking Read (one that waits until an event happens) look like this: ```python event, values = window.read() ``` A non-blocking / Async Read call looks like this: ```python event, values = window.read(timeout=100) ``` You can learn more about these async / non-blocking windows toward the end of this document. # Themes - Automatic Coloring of Your Windows In Dec 2019 the function `change_look_and_feel` was replaced by `theme`. The concept remains the same, but a new group of function calls makes it a lot easier to manage colors and other settings. By default the PySimpleGUI color theme is now `Dark Blue 3`. Gone are the "system default" gray colors. If you want your window to be devoid of all colors so that the system chooses the colors (gray) for you, then set the theme to 'gray gray gray'. This tells PySimpleGUI that you're a boring person.... no no no... I'm **just kidding!**.... it just means you want PySimpleGUI to not set any colors so that the default colors provided by the OS/tkinter will be used. It's a memorable theme name. There are several with "default" in the name and it got confusing which was the full-on-add-no-color name, so 'gray gray gray' was added to make it easy to recall. There are 154 themes available. You can preview these themes by calling `theme_previewer()` which will create a LARGE window displaying all of the color themes available. You can see the current available themes by calling `sg.theme_previewer()`. It creates a window that looked like this in June 2022: ![image](https://user-images.githubusercontent.com/46163555/173203488-7fef44b5-000f-48b9-b353-625c214e41d4.png) ## Default is `Dark Blue 3` ![image](https://user-images.githubusercontent.com/46163555/71362356-cd070200-2563-11ea-9455-9315b9423d7e.png) In Dec 2019 the default for all PySimpleGUI windows changed from the system gray with blue buttons to a more complete theme using a grayish blue with white text. Previously users were nagged into choosing color theme other than gray. Now it's done for you instead of nagging you. If you're struggling with this color theme, then add a call to `theme` to change it. ## Theme Name Formula Themes names that you specify can be "fuzzy". The text does not have to match exactly what you see printed. For example "Dark Blue 3" and "DarkBlue3" and "dark blue 3" all work. One way to quickly determine the best setting for your window is to simply display your window using a lot of different themes. Add the line of code to set the theme - `theme('Dark Green 1')`, run your code, see if you like it, if not, change the theme string to `'Dark Green 2'` and try again. Repeat until you find something you like. The "Formula" for the string is: `Dark Color #` or `Light Color #` Color can be Blue, Green, Black, Gray, Purple, Brown, Teal, Red. The # is optional or can be from 1 to XX. Some colors have a lot of choices. There are 13 "Light Brown" choices for example. ### "System" Default - No Colors (e.g. `"Gray Gray Gray"`) If you're bent on having no colors at all in your window, then choose `Default 1` or `System Default 1`. Use the `"Gray Gray Gray"` theme if you really want to ensure system defaults only. If you want the original PySimpleGUI color scheme of a blue button and everything else gray then you can get that with the theme `Default` or `System Default`. ## Theme Functions The basic theme function call is `theme(theme_name)`. This sets the theme. Calling without a parameter, `theme()` will return the name of the current theme. If you want to get or modify any of the theme settings, you can do it with these functions that you will find detailed information about in the function definitions section at the bottom of the document. Each will return the current value if no parameter is used. You'll find a detailed list of the theme calls in the SDK Call Reference in this section: https://pysimplegui.readthedocs.io/en/latest/call%20reference/#themes ```python theme theme_add_new theme_background_color theme_border_width theme_button_color theme_button_color_background theme_button_color_text theme_element_background_color theme_element_text_color theme_global theme_input_background_color theme_input_text_color theme_list theme_previewer theme_previewer_swatches theme_progress_bar_border_width theme_progress_bar_color theme_slider_border_width theme_slider_color theme_text_color theme_text_element_background_color theme_use_custom_titlebar ``` These will help you get a list of available choices. ```python theme_list theme_previewer ``` # Window Object - Beginning a window The first step is to create the window object using the desired window customizations. ## 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 existing 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 Window 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. ### Window Location PySimpleGUI computes the exact center of your window and centers the window on the screen. If you want to locate your window elsewhere, such as the system default of (0,0), if you have 2 ways of doing this. The first is when the window is created. Use the `location` parameter to set where the window. The second way of doing this is to use the `SetOptions` call which will set the default window location for all windows in the future. #### Multiple Monitors and Linux The auto-centering (default) location for your PySimpleGUI window may not be correct if you have multiple monitors on a Linux system. On Windows multiple monitors appear to work ok as the primary monitor the tkinter utilizes and reports on. Linux users with multiple monitors that have a problem when running with the default location will need to specify the location the window should be placed when creating the window by setting the `location` parameter. ### Window Size You can get your window's size by access the `Size` property. The window has to be Read once or Finalized in order for the value to be correct. Note that it's a property, not a call. `my_windows_size = window.Size` To finalize your window: ```python window = Window('My Title', layout, finalize=True) ``` ### Element Sizes There are multiple ways to set the size of Elements. They are: 1. The global default size - change using `SetOptions` function 2. At the Window level - change using the parameter `default_element_size` in your call to `Window` 3. At the Element level - each element has a `size` parameter Element sizes are measured in characters (there are exceptions). A Text Element with `size = (20,1)` has a size of 20 characters wide by 1 character tall. The default Element size for PySimpleGUI is `(45,1)`. There are a couple of widgets where one of the size values is in pixels rather than characters. This is true for Progress Meters and Sliders. The second parameter is the 'height' in pixels. ### No Titlebar Should you wish to create cool looking windows that are clean with no windows titlebar, use the no_titlebar option when creating the window. Be sure an provide your user an "exit" button or they will not be able to close the window! When no titlebar is enabled, there will be no icon on your taskbar for the window. Without an exit button you will need to kill via taskmanager... not fun. Windows with no titlebar rely on the grab anywhere option to be enabled or else you will be unable to move the window. Windows without a titlebar can be used to easily create a floating launcher. Linux users! Note that this setting has side effects for some of the other Elements. Multi-line input doesn't work at all, for example So, use with caution. ![floating launcher](https://user-images.githubusercontent.com/13696193/45258246-71bafb80-b382-11e8-9f5e-79421e6c00bb.jpg) ### Grab Anywhere This is a feature unique to PySimpleGUI. Note - there is a warning message printed out if the user closes a non-blocking window using a button with grab_anywhere enabled. There is no harm in these messages, but it may be distressing to the user. Should you wish to enable for a non-blocking window, simply get grab_anywhere = True when you create the window. ### Always on top To keep a window on top of all other windows on the screen, set keep_on_top = True when the window is created. This feature makes for floating toolbars that are very helpful and always visible on your desktop. ### Focus PySimpleGUI will set a default focus location for you. This generally means the first input field. You can set the focus to a particular element. If you are going to set the focus yourself, then you should turn off the automatic focus by setting `use_default_focus=False` in your Window call. ### TTK Buttons Beginning in release 4.7.0 PySimpleGUI supports both "normal" tk Buttons and ttk Buttons. This change was needed so that Mac users can use colors on their buttons. There is a bug that causes tk Buttons to not show text when you attempt to change the button color. Note that this problem goes away if you install Python from the official Python.org site rather than using Homebrew. A number of users have switched and are quite happy since even tk Buttons work on the Mac after the switch. By default Mac users will get ttk Buttons when a Button Element is used. All other platforms will get a normal tk Button. There are ways to override this behavior. One is by using the parameter `use_ttk_buttons` when you create your window. If set to True, all buttons will be ttk Buttons in the window. If set to False, all buttons will be normal tk Buttons. If not set then the platform or the Button Element determines which is used. If a system-wide setting is desired, then the default can be set using `set_options`. This will affect all windows such as popups and the debug window. ### TTK Themes tkinter has a number of "Themes" that can be used with ttk widgets. In PySimpleGUI these widgets include - Table, Tree, Combobox, Button, ProgressBar, Tabs & TabGroups. Some elements have a 'theme' parameter but these are no longer used and should be ignored. The initial release of PySimpleGUI attempted to mix themes in a single window but since have learned this is not possible so instead it is set at the Window or the system level. If a system-wide setting is desired, then the default can be set using `set_options`. This will affect all windows such as popups and the debug window. The ttk theme choices depend on the platform. Linux has a shorter number of selections than Windows. The minimum list of TTK themes is: - default - alt - clam - classic Most Windows systems have some additional themes that come standard with tkinter: - default - alt - clam - classic - winnative - vista - xpnative The list of available themes is populated automatically when you open the Global PySimpleGUI Settings window. Previously the list of themes was hard-coded by the PySimpleGUI code. Now the list is retrieved from tkinter. There are constants defined to help you with code completion to determine what your choices are. Theme constants start with `THEME_`. For example, the "clam" theme is `THEME_CLAM` If you try to manually set a TTK theme while making a Window or calling `set_options` and it is not a valid theme, you will be shown the list of valid themes in the error popup. The popup may resemble one like this: ![image](https://user-images.githubusercontent.com/46163555/167274285-14b95be3-5c5f-4edf-80e0-b8969ca3cf5c.png) ## Closing Windows When you are completely done with a window, you should close it and then delete it so that the resources, in particular the tkinter resources, are properly cleaned up. If you wish to do this in 1 line of code, here's your line: ```python window.close(); del window ``` The delete helps with a problem multi-threaded application encounter where tkinter complains that it is being called from the wrong thread (not the program's main thread) ## Window Methods That Complete Formation of Window After you have completed making your layout, stored in a variable called `layout` in these examples, you will create your window. The creation part of a window involves 3 steps. 1. Create a `Window` object 2. Adding your Layout to the window 3. Optional - Finalize if want to make changes prior to `read` call Over time the PySimpleGUI code has continued to compact, compress, so that as little code as possible will need to be written by the programmer. ### The Individual Calls This is the "long form" as each method is called individually. ```python window = sg.Window('My Title') window.layout(layout) window.finalize() ``` ### Chaining The Calls (the old method) The next level of compression that was done was to chain the calls together into a single line of code. ```python window = sg.Window('My Title').Layout(layout).finalize() ``` ### Using Parameters Instead of Calls (New Preferred Method) Here's a novel concept, instead of using chaining, something that's foreign to beginners, use parameters to the `Window` call. And that is exactly what's happened as of 4.2 of the PySimpleGUI port. ```python window = sg.Window('My Title', layout, finalize=True) ``` Rather than pushing the work onto the user of doing the layout and finalization calls, let the Window initialization code do it for you. Yea, it sounds totally obvious now, but it didn't a few months ago. This capability has been added to all 4 PySimpleGUI ports but none are on PyPI just yet as there is some runtime required first to make sure nothing truly bad is going to happen. Call to set the window layout. Must be called prior to `Read`. Most likely "chained" in line with the Window creation. ```python window = sg.Window('My window title', layout) ``` #### `finalize()` or `Window` parameter `finalize=True` Call to force a window to go through the final stages of initialization. This will cause the tkinter resources to be allocated so that they can then be modified. This also causes your window to appear. If you do not want your window to appear when Finalize is called, then set the Alpha to 0 in your window's creation parameters. If you want to call an element's `Update` method or call a `Graph` element's drawing primitives, you ***must*** either call `Read` or `Finalize` prior to making those calls. #### read(timeout=None, timeout_key=TIMEOUT_KEY, close=False) Read the Window's input values and button clicks in a blocking-fashion Returns event, values. Adding a timeout can be achieved by setting timeout=*number of milliseconds* before the Read times out after which a "timeout event" is returned. The value of timeout_key will be returned as the event. If you do not specify a timeout key, then the value `TIMEOUT_KEY` will be returned. If you set the timeout = 0, then the Read will immediately return rather than waiting for input or for a timeout. It's a truly non-blocking "read" of the window. # Layouts While at this point in the documentation you've not been shown very much about each Element available, you should read this section carefully as you can use the techniques you learn in here to build better, shorter, and easier to understand PySimpleGUI code. If it feels like this layout section is too much too soon, then come back to this section after you're learned about each Element. **Whatever order you find the least confusing is the best.** While you've not learned about Elements yet, it makes sense for this section to be up front so that you'll have learned how to use the elements prior to learning how each element works. At this point in your PySimpleGUI education, it is better for you to grasp time efficient ways of working with Elements than what each Element does. By learning now how to assemble Elements now, you'll have a good model to put the elements you learn into. There are *several* aspects of PySimpleGUI that make it more "Pythonic" than other Python GUI SDKs. One of the areas that is unique to PySimpleGUI is how a window's "layout" is defined, specified or built. A window's "layout" is simply a list of lists of elements. As you've already learned, these lists combine to form a complete window. This method of defining a window is super-powerful because lists are core to the Python language as a whole and thus are very easy to create and manipulate. Think about that for a moment and compare/contrast with Qt, tkinter, etc.. With PySimpleGUI the location of your element in a matrix determines where that Element is shown in the window. It's so ***simple*** and that makes it incredibly powerful. Want to switch a row in your GUI that has text with the one below it that has an input element? No problem, swap the lines of code and you're done. Layouts were designed to be visual. The idea is for you to be able to envision how a window will look by simply looking at the layout in the code. The CODE itself matches what is drawn on the screen. PySimpleGUI is a cross between straight Python code and a visual GUI designer. In the process of creating your window, you can manipulate these lists of elements without having an impact on the elements or on your window. Until you perform a "layout" of the list, they are nothing more than lists containing objects (they just happen to be your window's elements). Many times your window definition / layout will be a static, straightforward to create. However, window layouts are not limited to being one of these statically defined list of Elements. # Generated Layouts (For sure want to read if you have > 5 repeating elements/rows) There are 5 specific techniques of generating layouts discussed in this section. They can be used alone or in combination with each other. 1. Layout + Layout concatenation `[[A]] + [[B]] = [[A], [B]]` 2. Element Addition on Same Row `[[A] + [B]] = [[A, B]]` 3. List Comprehension to generate a row `[A for x in range(10)] = [A,A,A,A,A...]` 4. List Comprehension to generate multiple rows `[[A] for x in range(10)] = [[A],[A],...]` 5. User Defined Elements / Compound Elements ## Example - List Comprehension To Concatenate Multiple Rows - "To Do" List Example Let's create a little layout that will be used to make a to-do list using PySimpleGUI. ### Brute Force ```python import PySimpleGUI as sg layout = [ [sg.Text('1. '), sg.In(key=1)], [sg.Text('2. '), sg.In(key=2)], [sg.Text('3. '), sg.In(key=3)], [sg.Text('4. '), sg.In(key=4)], [sg.Text('5. '), sg.In(key=5)], [sg.Button('Save'), sg.Button('Exit')] ] window = sg.Window('To Do List Example', layout) event, values = window.read() ``` The output from this script was this window: ![SNAG-0451](https://user-images.githubusercontent.com/46163555/63563849-90cd8180-c530-11e9-80d7-4954b11deebd.jpg) Take a moment and look at the code and the window that's generated. Are you able to look at the layout and envision the Window on the screen? ### Build By Concatenating Rows The brute force method works great on a list that's 5 items long, but what if your todo list had 40 items on it. THEN what? Well, that's when we turn to a "generated" layout, a layout that is generated by your code. Replace the layout= stuff from the previous example with this definition of the layout. ```python import PySimpleGUI as sg layout = [] for i in range(1,6): layout += [sg.Text(f'{i}. '), sg.In(key=i)], layout += [[sg.Button('Save'), sg.Button('Exit')]] window = sg.Window('To Do List Example', layout) event, values = window.read() ``` It produces the exact same window of course. That's progress.... went from writing out every row of the GUI to generating every row. If we want 48 items as suggested, change the range(1,6) to range(1,48). Each time through the list another row is added onto the layout. ### Create Several Rows Using List Comprehension BUT, we're not done yet! This is **Python**, we're using lists to build something up, so we should be looking at ****list comprehensions****. Let's change the `for` loop into a list comprehension. Recall that our `for` loop was used to concatenate 6 rows into a layout. ```python layout = [[sg.Text(f'{i}. '), sg.In(key=i)] for i in range(1,6)] ``` Here we've moved the `for` loop to inside of the list definition (a list comprehension) ### Concatenating Multiple Rows We have our rows built using the list comprehension, now we just need the buttons. They can be easily "tacked onto the end" by simple addition. ```python layout = [[sg.Text(f'{i}. '), sg.In(key=i)] for i in range(1,6)] layout += [[sg.Button('Save'), sg.Button('Exit')]] ``` Anytime you have 2 layouts, you can concatenate them by simple addition. Make sure your layout is a "list of lists" layout. In the above example, we know the first line is a generated layout of the input rows. The last line adds onto the layout another layout... note the format being [ [ ] ]. This button definition is an entire layout, making it possible to add to our list comprehension `[[sg.Button('Save'), sg.Button('Exit')]]` It's quite readable code. The 2 layouts line up visually quite well. But let's not stop there with compressing the code. How about removing that += and instead change the layout into a single line with just a `+` between the two sets of row. Doing this concatenation on one line, we end up with this single statement that creates the **entire layout** for the GUI: ```python layout = [[sg.Text(f'{i}. '), sg.In(key=i)] for i in range(1,6)] + [[sg.Button('Save'), sg.Button('Exit')]] ``` ### Final "To Do List" Program And here we have our final program... all **4** lines. ```python import PySimpleGUI as sg layout = [[sg.Text(f'{i}. '), sg.In(key=i)] for i in range(1,6)] + [[sg.Button('Save'), sg.Button('Exit')]] window = sg.Window('To Do List Example', layout) event, values = window.read() ``` If you really wanted to crunch things down, you can make it a 2 line program (an import and 1 line of code) by moving the layout into the call to `Window` ```python import PySimpleGUI as sg event, values = sg.Window('To Do List Example', layout=[[sg.Text(f'{i}. '), sg.In(key=i)] for i in range(1,6)] + [[sg.Button('Save'), sg.Button('Exit')]]).read() ``` ## Example - List Comprehension to Build Rows - Table Simulation - Grid of Inputs In this example we're building a "table" that is 4 wide by 10 high using `Input` elements The end results we're seeking is something like this: ``` HEADER 1 HEADER 2 HEADER 3 HEADER 4 INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT INPUT ``` Once the code is completed, here is how the result will appear: ![image](https://user-images.githubusercontent.com/46163555/63626328-b4480900-c5d0-11e9-9c81-52e3b0516bde.png) We're going to be building each row using a list comprehension and we'll build the table by concatenating rows using another list comprehension. That's a list comprehension that goes across and another list comprehension that goes down the layout, adding one row after another. ### Building the Header First let's build the header. There are 2 concepts to notice here: ```python import PySimpleGUI as sg headings = ['HEADER 1', 'HEADER 2', 'HEADER 3','HEADER 4'] # the text of the headings header = [[sg.Text(' ')] + [sg.Text(h, size=(14,1)) for h in headings]] # build header layout ``` There are 2 things in this code to note 1. The list comprehension that makes the heading elements 2. The spaces added onto the front Let's start with the headers themselves. This is the code that makes a row of Text Elements containing the text for the headers. The result is a list of Text Elements, a row. ```python [sg.Text(h, size=(14,1)) for h in headings] ``` Then we add on a few spaces to shift the headers over so they are centered over their columns. We do this by simply adding a `Text` Element onto the front of that list of headings. ```python header = [[sg.Text(' ')] + [sg.Text(h, size=(14,1)) for h in headings]] ``` This `header` variable is a layout with 1 row that has a bunch of `Text` elements with the headings. ### Building the Input Elements The `Input` elements are arranged in a grid. To do this we will be using a double list comprehension. One will build the row the other will add the rows together to make the grid. Here's the line of code that does that: ```python input_rows = [[sg.Input(size=(15,1), pad=(0,0)) for col in range(4)] for row in range(10)] ``` This portion of the statement makes a single row of 4 `Input` Elements ```python [sg.Input(size=(15,1), pad=(0,0)) for col in range(4)] ``` Next we take that list of `Input` Elements and make as many of them as there are rows, 10 in this case. We're again using Python's awesome list comprehensions to add these rows together. ```python input_rows = [[sg.Input(size=(15,1), pad=(0,0)) for col in range(4)] for row in range(10)] ``` The first part should look familiar since it was just discussed as being what builds a single row. To make the matrix, we simply take that single row and create 10 of them, each being a list. ### Putting it all together Here is our final program that uses simple addition to add the headers onto the top of the input matrix. To make it more attractive, the color theme is set to 'Dark Brown 1'. ```python import PySimpleGUI as sg sg.theme('Dark Brown 1') headings = ['HEADER 1', 'HEADER 2', 'HEADER 3','HEADER 4'] header = [[sg.Text(' ')] + [sg.Text(h, size=(14,1)) for h in headings]] input_rows = [[sg.Input(size=(15,1), pad=(0,0)) for col in range(4)] for row in range(10)] layout = header + input_rows window = sg.Window('Table Simulation', layout, font='Courier 12') event, values = window.read() ``` ![image](https://user-images.githubusercontent.com/46163555/70472374-f7a18700-1a9c-11ea-9cd1-27d386cd9066.png) ## User Defined Elements / Compound Elements "User Defined Elements" and "Compound Elements" are one or more PySimpleGUI Elements that are wrapped in a function definition. In a layout, they have the appearance of being a custom elements of some type. User Defined Elements are particularly useful when you set a lot of parameters on an element that you use over and over in your layout. ### Example - A Grid of Buttons for Calculator App Let's say you're making a calculator application with buttons that have these settings: * font = Helvetica 20 * size = 5,1 * button color = white on blue The code for **one** of these buttons is: ```python sg.Button('1', button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20)) ``` If you have 6 buttons across and 5 down, your layout will have 30 of these lines of text. One row of these buttons could be written: ```python [sg.Button('1', button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20)), sg.Button('2', button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20)), sg.Button('3', button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20)), sg.Button('log', button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20)), sg.Button('ln', button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20)), sg.Button('-', button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20))], ``` By using User Defined Elements, you can significantly shorten your layouts. Let's call our element `CBtn`. It would be written like this: ```python def CBtn(button_text): return sg.Button(button_text, button_color=('white', 'blue'), size=(5, 1), font=("Helvetica", 20)) ``` Using your new `CBtn` Element, you could rewrite the row of buttons above as: ```python [CBtn('1'), CBtn('2'), CBtn('3'), CBtn('log'), CBtn('ln'), CBtn('-')], ``` See the tremendous amount of code you do not have to write! USE this construct any time you find yourself copying an element many times. But let's not stop there. Since we've been discussing list comprehensions, let's use them to create this row. The way to do that is to make a list of the symbols that go across the row make a loop that steps through that list. The result is a list that looks like this: ```python [CBtn(t) for t in ('1','2','3', 'log', 'ln', '-')], ``` That code produces the same list as this one we created by hand: ```python [CBtn('1'), CBtn('2'), CBtn('3'), CBtn('log'), CBtn('ln'), CBtn('-')], ``` ### Compound Elements Just like a `Button` can be returned from a User Defined Element, so can multiple Elements. Going back to the To-Do List example we did earlier, we could have defined a User Defined Element that represented a To-Do Item and this time we're adding a checkbox. A single line from this list will be: * The item # (a `Text` Element) * A `Checkbox` Element to indicate completed * An `Input` Element to type in what to do The definition of our User Element is this `ToDoItem` function. It is a single User Element that is a combination of 3 PySimpleGUI Elements. ```python def ToDoItem(num): return [sg.Text(f'{num}. '), sg.CBox(''), sg.In()] ``` This makes creating a list of 5 to-do items downright trivial when combined with the list comprehension techniques we learned earlier. Here is the code required to create 5 entries in our to-do list. ```python layout = [ToDoItem(x) for x in range(1,6)] ``` We can then literally add on the buttons ```python layout = [ToDoItem(x) for x in range(1,6)] + [[sg.Button('Save'), sg.Button('Exit')]] ``` And here is our final program ```python import PySimpleGUI as sg def ToDoItem(num): return [sg.Text(f'{num}. '), sg.CBox(''), sg.In()] layout = [ToDoItem(x) for x in range(1,6)] + [[sg.Button('Save'), sg.Button('Exit')]] window = sg.Window('To Do List Example', layout) event, values = window.read() ``` And the window it creates looks like this: ![image](https://user-images.githubusercontent.com/46163555/63628682-cda28280-c5db-11e9-92a4-44ec2cb6ccf9.png) --- # Elements You will find information on Elements and all other classes and functions are located in the Call Reference Tab of the documentation. "Elements" are the building blocks used to create windows. Some GUI APIs use the term "Widget" to describe these graphic elements. So that it's clear when a PySimpleGUI *Element* is being referenced versus an underlying GUI Framework's *Widget*. PySimpleGUI Elements map to a GUI Framework Widget, usually in a 1-to-1 manner. For example, a Text Element is implemented in tkinter using a Label Widget. ## Table of Elements in Tkinter Port Each port of PySimpleGUI has a core set of Elements as well as port-specific elements. Some port-specific elements include the Dial element in the Qt port, and the Pane element in the tkinter port. | Element Name | Aliases | tkinter Widget | Description | | :------------------ | :----------------------------- | :-------------------------------------- | :----------------------------------------------------------- | | Text | T, Txt | tk.Label | One or more lines of Text | | Input | I, In, InputText | tk.Entry | Single line text input | | Combo | DD, Drop, DropDown, InputCombo | ttk.Combobox | A "Dropdown" list that can be edited too | | OptionMenu | InputOptionMenu | tk.OptionMenu | Like a Combo | | Multiline | ML, MLine | tk.Text or tk.scrolledtext.ScrolledText | Multiple lines of text for input or output | | Output | | tk.Text (sorta) | Not suggested. Multiline is better choice. Re-routes stdout | | Radio | R, Rad | tk.Radiobutton | Radio Buttons - choose 1 from several | | Checkbox | CB, CBox, Check | tk.Checkbutton | Checkbox - binary choice Yes/No | | Spin | Sp | tk.Spinbox | Choose 1 by using arrows. Can manually enter also | | Button | B, Btn | tk.Button or ttk.Button | Button - plain or with image | | Image | Im | tk.Label | A PNG or GIF image | | Canvas | | tk.Canvas | A drawing area. Graph may be batter to use | | Column | Col | Combination Canvas & Frame | Embeds layouts inside layouts | | Frame | Fr | tk.LabelFrame | A frame with a title and a border | | Tab | | tk.Frame | Container used with TabGroup | | TabGroup | | ttk.Notebook | Holds Tabs in layouts | | Pane | | tk.PanedWindow | Sliding columns (it's kinda weird but useful) | | Graph | G | tk.Canvas | A drawing area with primitives | | Slider | Sl | tk.Scale | Slider to choose from range of choices | | Listbox | LB, LBox | tk.Listbox | Listbox - a list of choices | | Menu | MenuBar, Menubar | tk.Menu | A standard Menubar | | MenubarCustom | | Combined Elements | Custom colors and font for menubar | | ButtonMenu | BM, BMenu | tk.Menubutton | Button that shows a menu | | Titlebar | | Combined Elements | Custom colors for a titlebar | | ProgressBar | PBar, Prog, Progress | ttk.Progressbar | | | Table | | ttk.Treeview | A table with clickible cells and headers | | Tree | | ttk.Treeview | A tree with collapsible sections | | VerticalSeparator | VSep, VSeparator | ttk.Separator | Vertical line | | HorizontalSeparator | HSep, HSeparator | ttk.Separator | Horizontal line | | StatusBar | SBar | tk.Label | Statusbar for bottom of window | | Sizegrip | SGrip | ttk.Sizegrip | A grip for the bottom right corner of a window | | Push | P, Stretch | PySimpleGUI Elements | Pushes elements horizontally | | VPush | VP, VStretch | PySimpleGUI Elements | Pushes rows vertically | | Sizer | | Column Element | Creates a Width x Height number of pixels for padding/sizing | ## Layout Helper Functions Your Window's layout is composed of lists of Elements. In addition to elements, these Layout Help Functions may also be present in a layout definition | Layout Helper | Description | | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------- | | pin | "Pins" an element to a location in a layout. If element transitions from invisible to visible, pin ensures element is in correct location | | vtop | Vertically align element or row of elements to the top of the row | | vbottom | Vertically align element or row of elements to the bottom of the row | | vcenter | Vertically align element or row of elements to the center of the row | ## Keys ***Keys are a super important concept to understand in PySimpleGUI.*** If you are going to do anything beyond the basic stuff with your GUI, then you need to understand keys. You can think of a "key" as a "name" for an element. Or an "identifier". It's a way for you to identify and talk about an element with the PySimpleGUI library. It's the exact same kind of key as a dictionary key. They must be unique to a window. Keys are specified when the Element is created using the `key` parameter. Keys are used in these ways: * Specified when creating the element * Returned as events. If an element causes an event, its key will be used * In the `values` dictionary that is returned from `window.read()` * To make updates (changes), to an element that's in the window After you put a key in an element's definition, the values returned from `window.read` will use that key to tell you the value. For example, if you have an input element in your layout: `Input(key='mykey')` And your read looks like this: `event, values = Read()` Then to get the input value from the read it would be: `values['mykey']` You also use the same key if you want to call Update on an element. Please see the section Updating Elements to understand that usage. To get find an element object given the element's key, you can call the window method `find_element` (also written `FindElement`, `element`), or you can use the more common lookup mechanism: ```python window['key'] ``` While you'll often see keys written as strings in examples in this document, know that keys can be ***ANYTHING***. Let's say you have a window with a grid of input elements. You could use their row and column location as a key (a tuple) `key=(row, col)` Then when you read the `values` variable that's returned to you from calling `Window.read()`, the key in the `values` variable will be whatever you used to create the element. In this case you would read the values as: `values[(row, col)]` Most of the time they are simple text strings. In the Demo Programs, keys are written with this convention: `_KEY_NAME_` (underscore at beginning and end with all caps letters) or the most recent convention is to use a dash at the beginning and end (e.g. `'-KEY_NAME-'`). You don't have to follow the convention, but it's not a bad one to follow as other users are used to seeing this format and it's easy to spot when element keys are being used. If you have an element object, to find its key, access the member variable `.Key` for the element. This assumes you've got the element in a variable already. ```python text_elem = sg.Text('', key='-TEXT-') the_key = text_elem.Key ``` ### Default Keys If you fail to place a key on an Element, then one will be created for you automatically. For `Buttons`, the text on the button is that button's key. `Text` elements will default to the text's string (for when events are enabled and the text is clicked) If the element is one of the input elements (one that will cause an generate an entry in the return values dictionary) and you fail to specify one, then a number will be assigned to it beginning with the number 0. The effect will be as if the values are represented as a list even if a dictionary is used. ### Menu Keys Menu items can have keys associated with them as well. See the section on Menus for more information about these special keys. They aren't the same as Element keys. Like all elements, Menu Element have one of these Element keys. The individual menu item keys are different. ### `WRITE_ONLY_KEY` Modifier Sometimes you have input elements (e.g. `Multiline`) that you are using as an output. The contents of these elements may get very long. You don't need to "read" these elements and doing so will potentially needlessly return a lot of data. To tell PySimpleGUI that you do not want an element to return a value when `Window.read` is called, add the string `WRITE_ONLY_KEY` to your key name. If your `Multiline` element was defined like this originally: ```python sg.Multiline(size=(40,8), key='-MLINE-') ``` Then to turn off return values for that element, the `Multiline` element would be written like this: ```python 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