From c79a8772ccc15f1fc6efe1c03bef912660794160 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 15 Oct 2018 16:07:23 -0400 Subject: [PATCH] Renamed return value from Read call from button to event... EVERYWHERE --- Demo_All_Widgets.py | 14 +- Demo_Borderless_Window.py | 14 +- Demo_Button_Click.py | 4 +- Demo_Button_States.py | 14 +- Demo_Calendar.py | 4 +- Demo_Canvas.py | 8 +- Demo_Chat.py | 6 +- Demo_Chat_With_History.py | 12 +- Demo_Chatterbot.py | 4 +- Demo_Color.py | 6 +- Demo_Color_Names.py | 21 +- Demo_Color_Names_Smaller_List.py | 6 +- Demo_Columns.py | 4 +- Demo_Compare_Files.py | 4 +- Demo_DOC_Viewer_PIL.py | 24 +- Demo_Design_Patterns.py | 10 +- Demo_Desktop_Floating_Toolbar.py | 14 +- Demo_Desktop_Widget_CPU_Graph.py | 4 +- Demo_Desktop_Widget_CPU_Utilization.py | 4 +- Demo_Desktop_Widget_CPU_Utilization_Simple.py | 4 +- Demo_Desktop_Widget_Timer.py | 16 +- Demo_Disable_Elements.py | 27 +- Demo_Fill_Form.py | 8 +- Demo_Font_Sizer.py | 4 +- Demo_Font_String.py | 34 + Demo_Func_Callback_Simulation.py | 4 +- Demo_GoodColors.py | 2 +- Demo_Graph_Drawing.py | 13 +- Demo_Graph_Element.py | 4 +- Demo_Graph_Element_Sine_Wave.py | 4 +- Demo_Graph_Noise.py | 6 +- Demo_HowDoI.py | 18 +- Demo_Img_Viewer.py | 12 +- Demo_Keyboard.py | 14 +- Demo_Keyboard_Realtime.py | 16 +- Demo_Keypad.py | 12 +- Demo_LED_Indicators.py | 4 +- Demo_MIDI_Player.py | 10 +- Demo_Machine_Learning.py | 4 +- Demo_Matplotlib.py | 2 +- Demo_Matplotlib_Animated.py | 4 +- Demo_Matplotlib_Animated_Scatter.py | 4 +- Demo_Matplotlib_Browser.py | 6 +- Demo_Matplotlib_Ping_Graph.py | 4 +- Demo_Matplotlib_Ping_Graph_Large.py | 4 +- Demo_Media_Player.py | 8 +- Demo_Menus.py | 16 +- Demo_NonBlocking_Form.py | 8 +- Demo_OpenCV.py | 4 +- Demo_OpenCV_Webcam.py | 42 +- Demo_PDF_Viewer.py | 28 +- Demo_PNG_Thumbnail_Viewer.py | 136 + Demo_PNG_Viewer.py | 16 +- Demo_PSG_SDK_Quick_Ref.py | 45 +- Demo_Password_Login.py | 4 +- Demo_Pi_LEDs.py | 8 +- Demo_Pi_Robotics.py | 16 +- Demo_Ping_Line_Graph.py | 4 +- Demo_Pong.py | 14 +- Demo_Popup_Custom.py | 4 +- Demo_Popups.py | 6 + Demo_Progress_Meters.py | 12 +- Demo_Pyplot_Bar_Chart.py | 2 +- Demo_Pyploy_Bar_Chart2.py | 2 +- Demo_Script_Launcher.py | 12 +- Demo_Script_Parameters.py | 6 +- Demo_Spinner_Compound_Element.py | 6 +- Demo_Super_Simple_Form.py | 4 +- Demo_Table_CSV.py | 12 +- Demo_Table_Element.py | 15 +- Demo_Table_Pandas.py | 4 +- Demo_Table_Simulation.py | 8 +- Demo_Tabs.py | 8 +- Demo_Tabs_Nested.py | 6 +- Demo_Tabs_Simple.py | 7 +- Demo_Template.py | 8 +- Demo_Tree_Element.py | 8 +- Demo_Turtle.py | 59 +- Demo_Window_Disappear.py | 8 +- Demo_YouTube_Intro.py | 2 +- Demo_Youtube-dl_Frontend.py | 12 +- Demo_psutil_Kill_Processes.py | 22 +- PySimpleGUI27Gen.py | 5099 -------------- docs/cookbook.md | 2567 +++---- docs/index.md | 6018 +++++++++-------- readme.md | 6018 +++++++++-------- 86 files changed, 7992 insertions(+), 12709 deletions(-) create mode 100644 Demo_Font_String.py create mode 100644 Demo_PNG_Thumbnail_Viewer.py delete mode 100644 PySimpleGUI27Gen.py diff --git a/Demo_All_Widgets.py b/Demo_All_Widgets.py index fe5a9596..7a4d5a6e 100644 --- a/Demo_All_Widgets.py +++ b/Demo_All_Widgets.py @@ -13,14 +13,14 @@ menu_def = [['&File', ['&Open', '&Save', 'E&xit', 'Properties']], ['&Help', '&About...'], ] # ------ Column Definition ------ # -column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))], +column1 = [[sg.Text('Column 1', background_color='lightblue', justification='center', size=(10, 1))], [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')], [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')], [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]] layout = [ [sg.Menu(menu_def, tearoff=True)], - [sg.Text('All graphic widgets in one Window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE, text_color='midnightblue')], + [sg.Text('(Almost) All widgets in one Window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)], [sg.Text('Here is some text.... and a place to enter text')], [sg.InputText('This is my text')], [sg.Frame(layout=[ @@ -36,21 +36,19 @@ layout = [ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25), sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75), sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10), - sg.Column(column1, background_color='#F7F3EC')]])], + sg.Column(column1, background_color='lightblue')]])], [sg.Text('_' * 80)], [sg.Text('Choose A Folder', size=(35, 1))], [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Default Folder'), sg.FolderBrowse()], - [sg.Submit(tooltip='Click to submit this form'), sg.Cancel()] - ] + [sg.Submit(tooltip='Click to submit this form'), sg.Cancel()]] window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout) - -button, values = window.Read() +event, values = window.Read() sg.Popup('Title', 'The results of the window.', - 'The button clicked was "{}"'.format(button), + 'The button clicked was "{}"'.format(event), 'The values are', values) diff --git a/Demo_Borderless_Window.py b/Demo_Borderless_Window.py index f1e07b36..3f565b14 100644 --- a/Demo_Borderless_Window.py +++ b/Demo_Borderless_Window.py @@ -11,6 +11,7 @@ Turn off padding in order to get a really tight looking layout. sg.ChangeLookAndFeel('Dark') sg.SetOptions(element_padding=(0, 0)) + layout = [[sg.T('User:', pad=((3, 0), 0)), sg.OptionMenu(values=('User 1', 'User 2'), size=(20, 1)), sg.T('0', size=(8, 1))], [sg.T('Customer:', pad=((3, 0), 0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20, 1)), @@ -22,15 +23,20 @@ layout = [[sg.T('User:', pad=((3, 0), 0)), sg.OptionMenu(values=('User 1', 'User sg.ReadButton('Submit', button_color=('gray60', 'springgreen4')), sg.Button('Exit', button_color=('white', '#00406B'))]] -window = sg.Window("Time Tracker", default_element_size=(12, 1), text_justification='r', auto_size_text=False, - auto_size_buttons=False, no_titlebar=True, +window = sg.Window("Borderless Window", + default_element_size=(12, 1), + text_justification='r', + auto_size_text=False, + auto_size_buttons=False, + no_titlebar=True, + grab_anywhere=True, default_button_element_size=(12, 1)) window.Layout(layout) while True: - button, values = window.Read() - if button is None or button == 'Exit': + event, values = window.Read() + if event is None or event == 'Exit': break diff --git a/Demo_Button_Click.py b/Demo_Button_Click.py index 28528a46..9b2596eb 100644 --- a/Demo_Button_Click.py +++ b/Demo_Button_Click.py @@ -24,7 +24,7 @@ window.FindElement('submit').Update(disabled=True) recording = have_data = False while True: - button, values = window.Read() - if button is None: + event, values = window.Read() + if event is None: sys.exit(69) winsound.PlaySound("ButtonClick.wav", 1) diff --git a/Demo_Button_States.py b/Demo_Button_States.py index 9468bb12..6917ab28 100644 --- a/Demo_Button_States.py +++ b/Demo_Button_States.py @@ -30,22 +30,22 @@ for key, state in {'_Start_': False, '_Stop_': True, '_Reset_': True, '_Submit_' recording = have_data = False while True: - button, values = window.Read() - print(button) - if button is None: + event, values = window.Read() + print(event) + if event is None: sys.exit(69) - if button == '_Start_': + if event == '_Start_': for key, state in {'_Start_':True, '_Stop_':False, '_Reset_':False, '_Submit_':True}.items(): window.FindElement(key).Update(disabled=state) recording = True - elif button == '_Stop_' and recording: + elif event == '_Stop_' and recording: [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':False, '_Submit_':False}.items()] recording = False have_data = True - elif button == '_Reset_': + elif event == '_Reset_': [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':True, '_Submit_':True}.items()] recording = False have_data = False - elif button is '_Submit_' and have_data: + elif event is '_Submit_' and have_data: [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':True, '_Submit_':False}.items()] recording = False diff --git a/Demo_Calendar.py b/Demo_Calendar.py index 0b2df603..79a5533c 100644 --- a/Demo_Calendar.py +++ b/Demo_Calendar.py @@ -11,5 +11,5 @@ layout = [[sg.T('Calendar Test')], [sg.Ok(key=1)]] window = sg.Window('Calendar', grab_anywhere=False).Layout(layout) -b,v = window.Read() -sg.Popup(v['input']) +event,values = window.Read() +sg.Popup(values['input']) diff --git a/Demo_Canvas.py b/Demo_Canvas.py index 22d77b04..a310842c 100644 --- a/Demo_Canvas.py +++ b/Demo_Canvas.py @@ -15,10 +15,10 @@ window = sg.Window('Canvas test').Layout(layout).Finalize() cir = window.FindElement('canvas').TKCanvas.create_oval(50, 50, 100, 100) while True: - button, values = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - if button is 'Blue': + if event is 'Blue': window.FindElement('canvas').TKCanvas.itemconfig(cir, fill = "Blue") - elif button is 'Red': + elif event is 'Red': window.FindElement('canvas').TKCanvas.itemconfig(cir, fill = "Red") diff --git a/Demo_Chat.py b/Demo_Chat.py index 5b235c4c..9d6ad865 100644 --- a/Demo_Chat.py +++ b/Demo_Chat.py @@ -21,12 +21,12 @@ window = sg.Window('Chat window', default_element_size=(30, 2), font=('Helvetica # ---===--- Loop taking in user input and using it --- # while True: - (button, value) = window.Read() - if button is 'SEND': + (event, value) = window.Read() + if event is 'SEND': query = value['query'].rstrip() # EXECUTE YOUR COMMAND HERE print('The command you entered was {}'.format(query)) - elif button is None or button is 'EXIT': # quit if exit button or X + elif event is None or event == 'EXIT': # quit if exit button or X break sys.exit(69) diff --git a/Demo_Chat_With_History.py b/Demo_Chat_With_History.py index 652d4ca0..e30aeff9 100644 --- a/Demo_Chat_With_History.py +++ b/Demo_Chat_With_History.py @@ -32,8 +32,8 @@ def ChatBotWithHistory(): command_history = [] history_offset = 0 while True: - (button, value) = window.Read() - if button is 'SEND': + (event, value) = window.Read() + if event is 'SEND': query = value['query'].rstrip() # EXECUTE YOUR COMMAND HERE print('The command you entered was {}'.format(query)) @@ -41,17 +41,17 @@ def ChatBotWithHistory(): history_offset = len(command_history)-1 window.FindElement('query').Update('') # manually clear input because keyboard events blocks clear window.FindElement('history').Update('\n'.join(command_history[-3:])) - elif button is None or button is 'EXIT': # quit if exit button or X + elif event is None or event is 'EXIT': # quit if exit event or X break - elif 'Up' in button and len(command_history): + elif 'Up' in event and len(command_history): command = command_history[history_offset] history_offset -= 1 * (history_offset > 0) # decrement is not zero window.FindElement('query').Update(command) - elif 'Down' in button and len(command_history): + elif 'Down' in event and len(command_history): history_offset += 1 * (history_offset < len(command_history)-1) # increment up to end of list command = command_history[history_offset] window.FindElement('query').Update(command) - elif 'Escape' in button: + elif 'Escape' in event: window.FindElement('query').Update('') sys.exit(69) diff --git a/Demo_Chatterbot.py b/Demo_Chatterbot.py index 47ad334b..4bd8d355 100644 --- a/Demo_Chatterbot.py +++ b/Demo_Chatterbot.py @@ -66,8 +66,8 @@ window = sg.Window('Chat Window', auto_size_text=True, default_element_size=(30, # ---===--- Loop taking in user input and using it to query HowDoI web oracle --- # while True: - button, (value,) = window.Read() - if button is not 'SEND': + event, (value,) = window.Read() + if event is not 'SEND': break string = value.rstrip() print(' '+string) diff --git a/Demo_Color.py b/Demo_Color.py index 5dcc23af..11cd4c6b 100644 --- a/Demo_Color.py +++ b/Demo_Color.py @@ -1689,12 +1689,12 @@ def main(): [sg.Submit(), sg.Button('Many buttons', button_color=('white', '#0e6251'), key='Many buttons'), sg.ColorChooserButton( 'Chooser', target=(3,0), key='Chooser'), sg.Quit(),], ] # [g.Multiline(DefaultText=str(printable), Size=(30,20))]] - button, values = sg.Window('Color Demo', auto_size_buttons=False).Layout(layout).Read() + event, values = sg.Window('Color Demo', auto_size_buttons=False).Layout(layout).Read() # ------- OUTPUT results portion ------- # - if button == 'Quit' or button is None: + if event == 'Quit' or event is None: exit(0) - elif button == 'Many buttons': + elif event == 'Many buttons': show_all_colors_on_buttons() drop_down_value = values['listbox'] diff --git a/Demo_Color_Names.py b/Demo_Color_Names.py index ff46e2f8..e310d427 100644 --- a/Demo_Color_Names.py +++ b/Demo_Color_Names.py @@ -673,12 +673,21 @@ color_map = { sg.SetOptions(button_element_size=(12,1), element_padding=(0,0), auto_size_buttons=False, border_width=1, tooltip_time=100) -layout = [[sg.Text('Hover mouse to see RGB value, click for white & black text', text_color='blue', font='Any 15', relief=sg.RELIEF_SUNKEN, justification='center', size=(100,1), background_color='light green', pad=(0,(0,20))),]] +#start layout with the tittle +layout = [[sg.Text('Hover mouse to see RGB value, click for white & black text', + text_color='blue', + font='Any 15', + relief=sg.RELIEF_SUNKEN, + justification='center', + size=(100,1), + background_color='light green', + pad=(0,(0,20))),]] + +# -- Create primary color viewer window by building rows and appending to layout -- row = [] -# -- Create primary color viewer window -- for i, color in enumerate(color_map): row.append(sg.RButton(color, button_color=('black', color), key=color, tooltip=color_map[color])) - if (i+1) % 15 == 0: + if (i+1) % 15 == 0: # every 15 buttons make a new row layout.append(row) row = [] @@ -686,9 +695,9 @@ window = sg.Window('Color Viewer', grab_anywhere=False, font=('any 9')).Layout(l # -- Event loop -- while True: - b, v = window.Read() - if b is None: + event, values = window.Read() + if event is None: break # -- Create a secondary window that shows white and black text on chosen color - layout2 =[[sg.DummyButton(b, button_color=('white', b), tooltip=color_map[b]), sg.DummyButton(b, button_color=('black', b), tooltip=color_map[b])] ] + layout2 =[[sg.DummyButton(event, button_color=('white', event), tooltip=color_map[event]), sg.DummyButton(event, button_color=('black', event), tooltip=color_map[event])] ] sg.Window('Buttons with white and black text', keep_on_top=True).Layout(layout2).ReadNonBlocking() \ No newline at end of file diff --git a/Demo_Color_Names_Smaller_List.py b/Demo_Color_Names_Smaller_List.py index 4578c002..1cfaa34a 100644 --- a/Demo_Color_Names_Smaller_List.py +++ b/Demo_Color_Names_Smaller_List.py @@ -105,9 +105,9 @@ window = sg.Window('Color Viewer', grab_anywhere=False, font=('any 9')).Layout(l # -- Event loop -- while True: - b, v = window.Read() - if b is None: + event, values = window.Read() + if event is None: break # -- Create a secondary window that shows white and black text on chosen color - layout2 =[[sg.DummyButton(b, button_color=('white', b)), sg.DummyButton(b, button_color=('black', b))] ] + layout2 =[[sg.DummyButton(event, button_color=('white', event)), sg.DummyButton(event, button_color=('black', event))]] sg.Window('Buttons with white and black text', keep_on_top=True).Layout(layout2).ReadNonBlocking() \ No newline at end of file diff --git a/Demo_Columns.py b/Demo_Columns.py index dd68aa86..481751cb 100644 --- a/Demo_Columns.py +++ b/Demo_Columns.py @@ -19,7 +19,7 @@ layout = [[sg.Listbox(values=('Listbox Item 1', 'Listbox Item 2', 'Listbox Item [sg.OK()]] # Display the window and get values -button, values = sg.Window('Compact 1-line form with column').Layout(layout).Read() +event, values = sg.Window('Compact 1-line form with column').Layout(layout).Read() -sg.Popup(button, values, line_width=200) +sg.Popup(event, values, line_width=200) diff --git a/Demo_Compare_Files.py b/Demo_Compare_Files.py index a6a8c289..77ab262d 100644 --- a/Demo_Compare_Files.py +++ b/Demo_Compare_Files.py @@ -14,8 +14,8 @@ def GetFilesToCompare(): [sg.Submit(), sg.Cancel()]] window = sg.Window('File Compare') - button, values = window.Layout(form_rows).Read() - return button, values + event, values = window.Layout(form_rows).Read() + return event, values def main(): button, values = GetFilesToCompare() diff --git a/Demo_DOC_Viewer_PIL.py b/Demo_DOC_Viewer_PIL.py index 0287cc90..50c6f0c8 100644 --- a/Demo_DOC_Viewer_PIL.py +++ b/Demo_DOC_Viewer_PIL.py @@ -173,16 +173,16 @@ old_page = 0 old_zoom = False while True: - button, value = window.Read() - if button is None and (value is None or value['PageNumber'] is None): + event, value = window.Read() + if event is None and (value is None or value['PageNumber'] is None): break - if button in quit_buttons: + if event in quit_buttons: break zoom_pressed = False zoom = False - if button in enter_buttons: + if event in enter_buttons: try: cur_page = int(value['PageNumber']) - 1 # check if valid while cur_page < 0: @@ -190,19 +190,19 @@ while True: except: cur_page = 0 # this guy's trying to fool me - elif button in next_buttons: + elif event in next_buttons: cur_page += 1 - elif button in prev_buttons: + elif event in prev_buttons: cur_page -= 1 - elif button == Up: + elif event == Up: zoom = (clip_pos, 0, -1) - elif button == Down: + elif event == Down: zoom = (clip_pos, 0, 1) - elif button == Left: + elif event == Left: zoom = (clip_pos, -1, 0) - elif button == Right: + elif event == Right: zoom = (clip_pos, 1, 0) - elif button == "Zoom": + elif event == "Zoom": zoom_pressed = True zoom = (clip_pos, 0, 0) @@ -228,7 +228,7 @@ while True: old_zoom = zoom_pressed or zoom # update page number field - if button in my_keys: + if event in my_keys: goto.Update(str(cur_page + 1)) diff --git a/Demo_Design_Patterns.py b/Demo_Design_Patterns.py index aa56ddf0..dfe3ff35 100644 --- a/Demo_Design_Patterns.py +++ b/Demo_Design_Patterns.py @@ -18,7 +18,7 @@ else: layout = [[ sg.Text('My layout') ]] window = sg.Window('My window').Layout(layout) -button, value = window.Read() +event, values = window.Read() # -------------------------------------# @@ -35,8 +35,8 @@ layout = [[ sg.Text('My layout') ]] window = sg.Window('My new window').Layout(layout) while True: # Event Loop - button, value = window.Read() - if button is None: + event, values = window.Read() + if event is None: break # ------------------------------------------------------------------# @@ -53,6 +53,6 @@ layout = [[ sg.Text('My layout') ]] window = sg.Window('My new window').Layout(layout).Finalize() while True: # Event Loop - button, value = window.Read() - if button is None: + event, values = window.Read() + if event is None: break \ No newline at end of file diff --git a/Demo_Desktop_Floating_Toolbar.py b/Demo_Desktop_Floating_Toolbar.py index c50d8a90..204e0561 100644 --- a/Demo_Desktop_Floating_Toolbar.py +++ b/Demo_Desktop_Floating_Toolbar.py @@ -44,19 +44,19 @@ def Launcher(): # ---===--- Loop taking in user input and executing appropriate program --- # while True: - (button, value) = window.Read() - if button is 'EXIT' or button is None: + (event, values) = window.Read() + if event == 'EXIT' or event is None: break # exit button clicked - if button is 'Program 1': + if event == 'Program 1': print('Run your program 1 here!') - elif button is 'Program 2': + elif event == 'Program 2': print('Run your program 2 here!') - elif button is 'Run': - file = value['demofile'] + elif event == 'Run': + file = values['demofile'] print('Launching %s'%file) ExecuteCommandSubprocess('python', os.path.join(ROOT_PATH, file)) else: - print(button) + print(event) def ExecuteCommandSubprocess(command, *args, wait=False): try: diff --git a/Demo_Desktop_Widget_CPU_Graph.py b/Demo_Desktop_Widget_CPU_Graph.py index 9f2f8984..1af8fd2a 100644 --- a/Demo_Desktop_Widget_CPU_Graph.py +++ b/Demo_Desktop_Widget_CPU_Graph.py @@ -54,8 +54,8 @@ def main(): prev_x, prev_y = 0, 0 while True: # the Event Loop time.sleep(.5) - button, values = window.ReadNonBlocking() - if button == 'Quit' or values is None: # always give ths user a way out + event, values = window.ReadNonBlocking() + if event == 'Quit' or values is None: # always give ths user a way out break # do CPU measurement and graph it current_cpu = int(g_cpu_percent*10) diff --git a/Demo_Desktop_Widget_CPU_Utilization.py b/Demo_Desktop_Widget_CPU_Utilization.py index 97de5af6..00b03682 100644 --- a/Demo_Desktop_Widget_CPU_Utilization.py +++ b/Demo_Desktop_Widget_CPU_Utilization.py @@ -62,10 +62,10 @@ def main(): # ---------------- main loop ---------------- while (True): # --------- Read and update window -------- - button, values = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() # --------- Do Button Operations -------- - if values is None or button == 'Exit': + if values is None or event == 'Exit': break try: g_interval = int(values['spin']) diff --git a/Demo_Desktop_Widget_CPU_Utilization_Simple.py b/Demo_Desktop_Widget_CPU_Utilization_Simple.py index 460e2692..2558ee2b 100644 --- a/Demo_Desktop_Widget_CPU_Utilization_Simple.py +++ b/Demo_Desktop_Widget_CPU_Utilization_Simple.py @@ -25,10 +25,10 @@ window = sg.Window('CPU Meter', # ---------------- main loop ---------------- while (True): # --------- Read and update window -------- - button, values = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() # --------- Do Button Operations -------- - if values is None or button == 'Exit': + if values is None or event == 'Exit': break try: interval = int(values['_spin_']) diff --git a/Demo_Desktop_Widget_Timer.py b/Demo_Desktop_Widget_Timer.py index c567c9d2..e2653e3e 100644 --- a/Demo_Desktop_Widget_Timer.py +++ b/Demo_Desktop_Widget_Timer.py @@ -32,25 +32,25 @@ start_time = int(round(time.time() * 100)) while (True): # --------- Read and update window -------- if not paused: - button, values = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() current_time = int(round(time.time() * 100)) - start_time else: - button, values = window.Read() - if button == 'button': - button = window.FindElement(button).GetText() + event, values = window.Read() + if event == 'button': + event = window.FindElement(event).GetText() # --------- Do Button Operations -------- - if values is None or button == 'Exit': + if values is None or event == 'Exit': break - if button is 'Reset': + if event is 'Reset': start_time = int(round(time.time() * 100)) current_time = 0 paused_time = start_time - elif button == 'Pause': + elif event == 'Pause': paused = True paused_time = int(round(time.time() * 100)) element = window.FindElement('button') element.Update(text='Run') - elif button == 'Run': + elif event == 'Run': paused = False start_time = start_time + int(round(time.time() * 100)) - paused_time element = window.FindElement('button') diff --git a/Demo_Disable_Elements.py b/Demo_Disable_Elements.py index d927d5a1..61c6dd30 100644 --- a/Demo_Disable_Elements.py +++ b/Demo_Disable_Elements.py @@ -12,35 +12,34 @@ sg.SetOptions(element_padding=(0, 0)) layout = [ [sg.T('Notes:', pad=((3, 0), 0)), sg.In(size=(44, 1), background_color='white', text_color='black', key='notes')], [sg.T('Output:', pad=((3, 0), 0)), sg.T('', size=(44, 1), text_color='white', key='output')], - [sg.CBox('Checkbox:', default=True, pad=((3, 0), 0), key='cbox'), sg.Listbox((1,2,3,4),size=(8,3),key='listbox'), - sg.Radio('Radio 1', default=True, group_id='1', key='radio1'), sg.Radio('Radio 2', default=False, group_id='1', key='radio2')], - [sg.Spin((1,2,3,4),1, key='spin'), sg.OptionMenu((1,2,3,4), key='option'), sg.Combo(values=(1,2,3,4),key='combo')], - [sg.Multiline('Multiline', size=(20,3), key='multi')], - [sg.Slider((1,10), size=(20,20), orientation='h', key='slider')], + [sg.CBox('Checkbox:', default=True, pad=((3, 0), 0), disabled=True, key='cbox'), sg.Listbox((1,2,3,4),size=(8,3),disabled=True, key='listbox'), + sg.Radio('Radio 1', default=True, group_id='1', disabled=True, key='radio1'), sg.Radio('Radio 2', default=False, group_id='1', disabled=True, key='radio2')], + [sg.Spin((1,2,3,4),1,disabled=True, key='spin'), sg.OptionMenu((1,2,3,4),disabled=True, key='option'), sg.Combo(values=(1,2,3,4),disabled=True,key='combo')], + [sg.Multiline('Multiline', size=(20,3),disabled=True, key='multi')], + [sg.Slider((1,10), size=(20,20), orientation='h', disabled=True, key='slider')], [sg.ReadButton('Enable', button_color=('white', 'black')), sg.ReadButton('Disable', button_color=('white', 'black')), sg.ReadButton('Reset', button_color=('white', '#9B0023'), key='reset'), sg.ReadButton('Values', button_color=('white', 'springgreen4')), - sg.Button('Exit', button_color=('white', '#00406B'))]] + sg.Button('Exit', disabled=True, button_color=('white', '#00406B'), key='exit')]] window = sg.Window("Disable Elements Demo", default_element_size=(12, 1), text_justification='r', auto_size_text=False, auto_size_buttons=False, keep_on_top=True, grab_anywhere=False, default_button_element_size=(12, 1)).Layout(layout).Finalize() -key_list = 'cbox', 'listbox', 'radio1', 'radio2', 'spin', 'option', 'combo', 'reset', 'notes', 'multi', 'slider' +key_list = 'cbox', 'listbox', 'radio1', 'radio2', 'spin', 'option', 'combo', 'reset', 'notes', 'multi', 'slider', 'exit' for key in key_list: window.FindElement(key).Update(disabled=True) # don't do this kind of for-loop while True: - button, values = window.Read() - if button is None or button == 'Exit': + event, values = window.Read() + if event in (None, 'Exit'): break - elif button == 'Disable': + elif event == 'Disable': for key in key_list: window.FindElement(key).Update(disabled=True) - elif button == 'Enable': + elif event == 'Enable': for key in key_list: window.FindElement(key).Update(disabled=False) - elif button == 'Values': + elif event == 'Values': sg.Popup(values, keep_on_top=True) - - +sys.exit(0) diff --git a/Demo_Fill_Form.py b/Demo_Fill_Form.py index cbfed70e..2aa3a6f5 100644 --- a/Demo_Fill_Form.py +++ b/Demo_Fill_Form.py @@ -46,17 +46,17 @@ def Everything(): window.Layout(layout) while True: - button, values = window.Read() + event, values = window.Read() - if button is 'SaveSettings': + if event is 'SaveSettings': filename = sg.PopupGetFile('Save Settings', save_as=True, no_window=True) window.SaveToDisk(filename) # save(values) - elif button is 'LoadSettings': + elif event is 'LoadSettings': filename = sg.PopupGetFile('Load Settings', no_window=True) window.LoadFromDisk(filename) # load(form) - elif button in ['Exit', None]: + elif event in ['Exit', None]: break # window.CloseNonBlocking() diff --git a/Demo_Font_Sizer.py b/Demo_Font_Sizer.py index 56983819..7050160e 100644 --- a/Demo_Font_Sizer.py +++ b/Demo_Font_Sizer.py @@ -14,8 +14,8 @@ sz = fontSize window = sg.Window("Font size selector", grab_anywhere=False) window.Layout(layout) while True: - button, values= window.Read() - if button is None or button == 'Quit': + event, values= window.Read() + if event is None or event == 'Quit': break sz_spin = int(values['spin']) sz_slider = int(values['slider']) diff --git a/Demo_Font_String.py b/Demo_Font_String.py new file mode 100644 index 00000000..b32ef663 --- /dev/null +++ b/Demo_Font_String.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +import sys +if sys.version_info[0] >= 3: + import PySimpleGUI as sg +else: + import PySimpleGUI27 as sg + +layout = [[sg.Text('This is my sample text',size=(20,1), key='_text_') ], + [sg.CB('Bold', key='_bold_', change_submits=True), + sg.CB('Italics', key='_italics_', change_submits=True), + sg.CB('Underline', key='_underline_', change_submits=True)], + [sg.Slider((6,50), default_value=12, size=(14,20), orientation='h', key='_slider_', change_submits=True), + sg.Text('Font size')], + [sg.Text('Font string = '), sg.Text('', size=(25,1), key='_fontstring_')], + [ sg.RButton('Exit')]] + +window = sg.Window('Font string builder').Layout(layout) + +text_elem = window.FindElement('_text_') +while True: # Event Loop + event, values = window.Read() + if event in (None, 'Exit'): + break + font_string = 'Helvitica ' + font_string += str(values['_slider_']) + if values['_bold_']: + font_string += ' bold' + if values['_italics_']: + font_string += ' italic' + if values['_underline_']: + font_string += ' underline' + text_elem.Update(font=font_string) + window.FindElement('_fontstring_').Update('"'+font_string+'"') + print(event, values) diff --git a/Demo_Func_Callback_Simulation.py b/Demo_Func_Callback_Simulation.py index a6bf0095..d2996c4a 100644 --- a/Demo_Func_Callback_Simulation.py +++ b/Demo_Func_Callback_Simulation.py @@ -9,11 +9,11 @@ layout = [[sg.Text('Filename', )], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()]] -button, (number,) = sg.Window('Get filename example').LayoutAndRead(layout) +event, (number,) = sg.Window('Get filename example').LayoutAndRead(layout) import PySimpleGUI as sg -button, (filename,) = sg.Window('Get filename example').LayoutAndRead( +event, (filename,) = sg.Window('Get filename example').LayoutAndRead( [[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()]]) \ No newline at end of file diff --git a/Demo_GoodColors.py b/Demo_GoodColors.py index 8ec0fee7..8ea1ba9c 100644 --- a/Demo_GoodColors.py +++ b/Demo_GoodColors.py @@ -47,7 +47,7 @@ def main(): #===== Add a click me button for fun and SHOW the window ===== ===== ===== ===== ===== ===== =====# window.AddRow(sg.Button('Click ME!')) - (button, value) = window.Show() # show it! + event, values = window.Read() # show it! if __name__ == '__main__': diff --git a/Demo_Graph_Drawing.py b/Demo_Graph_Drawing.py index c211a3cf..232acff8 100644 --- a/Demo_Graph_Drawing.py +++ b/Demo_Graph_Drawing.py @@ -16,17 +16,18 @@ point = graph.DrawPoint((75,75), 10, color='green') oval = graph.DrawOval((25,300), (100,280), fill_color='purple', line_color='purple' ) rectangle = graph.DrawRectangle((25,300), (100,280), line_color='purple' ) line = graph.DrawLine((0,0), (100,100)) - +arc = graph.DrawArc((0,0), (400,400), 160, 10, style='arc' ,arc_color='blue') while True: - button, values = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - if button is 'Blue': + if event is 'Blue': graph.TKCanvas.itemconfig(circle, fill = "Blue") - elif button is 'Red': + elif event is 'Red': graph.TKCanvas.itemconfig(circle, fill = "Red") - elif button is 'Move': + elif event is 'Move': graph.MoveFigure(point, 10,10) graph.MoveFigure(circle, 10,10) graph.MoveFigure(oval, 10,10) graph.MoveFigure(rectangle, 10,10) + graph.MoveFigure(arc, 10,10) diff --git a/Demo_Graph_Element.py b/Demo_Graph_Element.py index e683f279..51aa7363 100644 --- a/Demo_Graph_Element.py +++ b/Demo_Graph_Element.py @@ -45,8 +45,8 @@ def main(): while True: time.sleep(.2) - button, values = window.ReadNonBlocking() - if button == 'Quit' or values is None: + event, values = window.ReadNonBlocking() + if event == 'Quit' or values is None: break if g_response_time is None or prev_response_time == g_response_time: continue diff --git a/Demo_Graph_Element_Sine_Wave.py b/Demo_Graph_Element_Sine_Wave.py index 997d804b..c7154f38 100644 --- a/Demo_Graph_Element_Sine_Wave.py +++ b/Demo_Graph_Element_Sine_Wave.py @@ -34,7 +34,7 @@ for y in range(-100, 101, 20): # Draw Graph for x in range(-100,100): - y = math.sin(x/20)*50 + y = math.sin(x/30)*50 graph.DrawCircle((x,y), 1, line_color='red', fill_color='red') -button, values = window.Read() +event, values = window.Read() diff --git a/Demo_Graph_Noise.py b/Demo_Graph_Noise.py index 8af8cf15..2ed56a32 100644 --- a/Demo_Graph_Noise.py +++ b/Demo_Graph_Noise.py @@ -45,9 +45,9 @@ def main(): graph_value = 250 while True: # time.sleep(.2) - button, values = window.ReadNonBlocking() - print(button, values) - if button == 'Quit' or values is None: + event, values = window.ReadNonBlocking() + print(event, values) + if event == 'Quit' or values is None: break graph_offset = random.randint(-10, 10) graph_value = graph_value + graph_offset diff --git a/Demo_HowDoI.py b/Demo_HowDoI.py index e05db15b..bcd53d2c 100644 --- a/Demo_HowDoI.py +++ b/Demo_HowDoI.py @@ -41,26 +41,26 @@ def HowDoI(): command_history = [] history_offset = 0 while True: - (button, value) = window.Read() - if button is 'SEND': - query = value['query'].rstrip() - print(query) - QueryHowDoI(query, value['Num Answers'], value['full text']) # send the string to HowDoI + event, values = window.Read() + if event == 'SEND': + query = values['query'].rstrip() + # print(query) + QueryHowDoI(query, values['Num Answers'], values['full text']) # send the string to HowDoI command_history.append(query) history_offset = len(command_history)-1 window.FindElement('query').Update('') # manually clear input because keyboard events blocks clear window.FindElement('history').Update('\n'.join(command_history[-3:])) - elif button is None or button is 'EXIT': # if exit button or closed using X + elif event == None or event == 'EXIT': # if exit button or closed using X break - elif 'Up' in button and len(command_history): # scroll back in history + elif 'Up' in event and len(command_history): # scroll back in history command = command_history[history_offset] history_offset -= 1 * (history_offset > 0) # decrement is not zero window.FindElement('query').Update(command) - elif 'Down' in button and len(command_history): # scroll forward in history + elif 'Down' in event and len(command_history): # scroll forward in history history_offset += 1 * (history_offset < len(command_history)-1) # increment up to end of list command = command_history[history_offset] window.FindElement('query').Update(command) - elif 'Escape' in button: # clear currently line + elif 'Escape' in event: # clear currently line window.FindElement('query').Update('') diff --git a/Demo_Img_Viewer.py b/Demo_Img_Viewer.py index 7e76849f..627e507d 100644 --- a/Demo_Img_Viewer.py +++ b/Demo_Img_Viewer.py @@ -88,22 +88,22 @@ window.Layout(layout) # Shows form on screen i=0 while True: # read the form - button, values = window.Read() - print(button, values) + event, values = window.Read() + print(event, values) # perform button and keyboard operations - if button is None: + if event is None: break - elif button in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34'): + elif event in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34'): i += 1 if i >= num_files: i -= num_files filename = os.path.join(folder, fnames[i]) - elif button in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33'): + elif event in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33'): i -= 1 if i < 0: i = num_files + i filename = os.path.join(folder, fnames[i]) - elif button == 'listbox': # something from the listbox + elif event == 'listbox': # something from the listbox f = values["listbox"][0] # selected filename filename = os.path.join(folder, f) # read this file i = fnames.index(f) # update running index diff --git a/Demo_Keyboard.py b/Demo_Keyboard.py index 11510604..6fd21931 100644 --- a/Demo_Keyboard.py +++ b/Demo_Keyboard.py @@ -16,14 +16,14 @@ window = sg.Window("Keyboard Test", return_keyboard_events=True, use_default_foc # ---===--- Loop taking in user input --- # while True: - button, value = window.Read() + event, values = window.Read() text_elem = window.FindElement('text') - if button in ("OK", None): - print(button, "exiting") + if event in ("OK", None): + print(event, "exiting") break - if len(button) == 1: - text_elem.Update(value='%s - %s' % (button, ord(button))) - if button is not None: - text_elem.Update(button) + if len(event) == 1: + text_elem.Update(value='%s - %s' % (event, ord(event))) + if event is not None: + text_elem.Update(event) diff --git a/Demo_Keyboard_Realtime.py b/Demo_Keyboard_Realtime.py index 460d79d1..6143bf87 100644 --- a/Demo_Keyboard_Realtime.py +++ b/Demo_Keyboard_Realtime.py @@ -11,15 +11,15 @@ layout = [[sg.Text("Hold down a key")], window = sg.Window("Realtime Keyboard Test", return_keyboard_events=True, use_default_focus=False).Layout(layout) while True: - button, value = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() - if button == "OK": - print(button, value, "exiting") + if event == "OK": + print(event, values, "exiting") break - if button is not None: - if len(button) == 1: - print('%s - %s'%(button, ord(button))) + if event is not None: + if len(event) == 1: + print('%s - %s' % (event, ord(event))) else: - print(button) - elif value is None: + print(event) + elif values is None: break diff --git a/Demo_Keypad.py b/Demo_Keypad.py index c99e6dd3..c5cc4ff2 100644 --- a/Demo_Keypad.py +++ b/Demo_Keypad.py @@ -29,15 +29,15 @@ window = sg.Window('Keypad', default_button_element_size=(5, 2), auto_size_butto # Loop forever reading the form's values, updating the Input field keys_entered = '' while True: - button, values = window.Read() # read the form - if button is None: # if the X button clicked, just exit + event, values = window.Read() # read the form + if event is None: # if the X button clicked, just exit break - if button == 'Clear': # clear keys if clear button + if event == 'Clear': # clear keys if clear button keys_entered = '' - elif button in '1234567890': + elif event in '1234567890': keys_entered = values['input'] # get what's been entered so far - keys_entered += button # add the new digit - elif button == 'Submit': + keys_entered += event # add the new digit + elif event == 'Submit': keys_entered = values['input'] window.FindElement('out').Update(keys_entered) # output the final string diff --git a/Demo_LED_Indicators.py b/Demo_LED_Indicators.py index 809eb54b..06961874 100644 --- a/Demo_LED_Indicators.py +++ b/Demo_LED_Indicators.py @@ -39,8 +39,8 @@ window = sg.Window('My new window', default_element_size=(12, 1), auto_size_text i = 0 while True: # Event Loop - button, value = window.ReadNonBlocking() - if button == 'Exit': + event, value = window.ReadNonBlocking() + if event == 'Exit': window.CloseNonBlocking() break if value is None: diff --git a/Demo_MIDI_Player.py b/Demo_MIDI_Player.py index dfac6460..79c73e3d 100644 --- a/Demo_MIDI_Player.py +++ b/Demo_MIDI_Player.py @@ -90,16 +90,16 @@ class PlayerGUI(): if 'window' not in locals() or window is None: # if the widnow has been destoyed don't mess with it return PLAYER_COMMAND_EXIT self.TextElem.Update(DisplayString) - button, (values) = window.ReadNonBlocking() + event, (values) = window.ReadNonBlocking() if values is None: return PLAYER_COMMAND_EXIT - if button == 'PAUSE': + if event == 'PAUSE': return PLAYER_COMMAND_PAUSE - elif button == 'EXIT': + elif event == 'EXIT': return PLAYER_COMMAND_EXIT - elif button == 'NEXT': + elif event == 'NEXT': return PLAYER_COMMAND_NEXT - elif button == 'Restart Song': + elif event == 'Restart Song': return PLAYER_COMMAND_RESTART_SONG return PLAYER_COMMAND_NONE diff --git a/Demo_Machine_Learning.py b/Demo_Machine_Learning.py index 42b1caa6..3ef1743a 100644 --- a/Demo_Machine_Learning.py +++ b/Demo_Machine_Learning.py @@ -52,8 +52,8 @@ def CustomMeter(): # loop that would normally do something useful for i in range(10000): # check to see if the cancel button was clicked and exit loop if clicked - button, values = window.ReadNonBlocking() - if button == 'Cancel' or values == None: + event, values = window.ReadNonBlocking() + if event == 'Cancel' or values == None: break # update bar with loop value +1 so that bar eventually reaches the maximum progress_bar.UpdateBar(i+1) diff --git a/Demo_Matplotlib.py b/Demo_Matplotlib.py index 4019194c..26ea19b1 100644 --- a/Demo_Matplotlib.py +++ b/Demo_Matplotlib.py @@ -112,4 +112,4 @@ window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', for fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig) # show it all again and get buttons -button, values = window.Read() +event, values = window.Read() diff --git a/Demo_Matplotlib_Animated.py b/Demo_Matplotlib_Animated.py index 7ea71601..764e3e76 100644 --- a/Demo_Matplotlib_Animated.py +++ b/Demo_Matplotlib_Animated.py @@ -36,8 +36,8 @@ def main(): dpts = [randint(0, 10) for x in range(10000)] for i in range(len(dpts)): - button, values = window.ReadNonBlocking() - if button is 'Exit' or values is None: + event, values = window.ReadNonBlocking() + if event is 'Exit' or values is None: exit(69) slider_elem.Update(i) diff --git a/Demo_Matplotlib_Animated_Scatter.py b/Demo_Matplotlib_Animated_Scatter.py index 49135aa8..98cb7bb4 100644 --- a/Demo_Matplotlib_Animated_Scatter.py +++ b/Demo_Matplotlib_Animated_Scatter.py @@ -26,8 +26,8 @@ def main(): canvas = canvas_elem.TKCanvas while True: - button, values = window.ReadNonBlocking() - if button is 'Exit' or values is None: + event, values = window.ReadNonBlocking() + if event is 'Exit' or values is None: exit(69) def PyplotScatterWithLegend(): diff --git a/Demo_Matplotlib_Browser.py b/Demo_Matplotlib_Browser.py index f3095678..ed927c96 100644 --- a/Demo_Matplotlib_Browser.py +++ b/Demo_Matplotlib_Browser.py @@ -886,10 +886,10 @@ canvas_elem = window.FindElement('canvas') multiline_elem= window.FindElement('multiline') while True: - button, values = window.Read() - print(button) + event, values = window.Read() + print(event) # show it all again and get buttons - if button is None or button is 'Exit': + if event is None or event is 'Exit': break try: diff --git a/Demo_Matplotlib_Ping_Graph.py b/Demo_Matplotlib_Ping_Graph.py index 31829750..7532bb04 100644 --- a/Demo_Matplotlib_Ping_Graph.py +++ b/Demo_Matplotlib_Ping_Graph.py @@ -662,8 +662,8 @@ def main(): plt.tight_layout() while True: - button, values = window.ReadNonBlocking() - if button is 'Exit' or values is None: + event, values = window.ReadNonBlocking() + if event is 'Exit' or values is None: exit(0) run_a_ping_and_graph() diff --git a/Demo_Matplotlib_Ping_Graph_Large.py b/Demo_Matplotlib_Ping_Graph_Large.py index a7328a70..f693e109 100644 --- a/Demo_Matplotlib_Ping_Graph_Large.py +++ b/Demo_Matplotlib_Ping_Graph_Large.py @@ -96,8 +96,8 @@ def main(): plt.tight_layout() while True: - button, values = window.ReadNonBlocking() - if button is 'Exit' or values is None: + event, values = window.ReadNonBlocking() + if event is 'Exit' or values is None: break run_a_ping_and_graph() diff --git a/Demo_Media_Player.py b/Demo_Media_Player.py index 1a4e0d5f..d6bb2915 100644 --- a/Demo_Media_Player.py +++ b/Demo_Media_Player.py @@ -57,12 +57,12 @@ def MediaPlayerGUI(): # Our event loop while(True): # Read the form (this call will not block) - button, values = window.ReadNonBlocking() - if button == 'Exit': + event, values = window.ReadNonBlocking() + if event == 'Exit': break # If a button was pressed, display it on the GUI by updating the text element - if button: - window.FindElement('output').Update(button) + if event: + window.FindElement('output').Update(event) MediaPlayerGUI() diff --git a/Demo_Menus.py b/Demo_Menus.py index 0849cd49..e916563f 100644 --- a/Demo_Menus.py +++ b/Demo_Menus.py @@ -43,19 +43,19 @@ def TestMenus(): # ------ Loop & Process button menu choices ------ # while True: - button, values = window.Read() - if button is None or button == 'Exit': + event, values = window.Read() + if event is None or event == 'Exit': return - print('Button = ', button) + print('Event = ', event) # ------ Process menu choices ------ # - if button == 'About...': - window.Hide() + if event == 'About...': + # window.Hide() sg.Popup('About this program','Version 1.0', 'PySimpleGUI rocks...', grab_anywhere=True) - window.UnHide() - elif button == 'Open': + # window.UnHide() + elif event == 'Open': filename = sg.PopupGetFile('file to open', no_window=True) print(filename) - elif button == 'Properties': + elif event == 'Properties': SecondForm() TestMenus() \ No newline at end of file diff --git a/Demo_NonBlocking_Form.py b/Demo_NonBlocking_Form.py index 0c489c69..fc5273e0 100644 --- a/Demo_NonBlocking_Form.py +++ b/Demo_NonBlocking_Form.py @@ -26,13 +26,13 @@ def StatusOutputExample(): i=0 while (True): # This is the code that reads and updates your window - button, values = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format((i // 100) // 60, (i // 100) % 60, i % 100)) - if button == 'Quit' or values is None: + if event == 'Quit' or values is None: break - if button == 'LED On': + if event == 'LED On': print('Turning on the LED') - elif button == 'LED Off': + elif event == 'LED Off': print('Turning off the LED') i += 1 diff --git a/Demo_OpenCV.py b/Demo_OpenCV.py index a0260796..3e1b0ec6 100644 --- a/Demo_OpenCV.py +++ b/Demo_OpenCV.py @@ -40,8 +40,8 @@ def main(): # ---===--- LOOP through video file by frame --- # i = 0 while vidFile.isOpened(): - button, values = window.ReadNonBlocking() - if button is 'Exit' or values is None: + event, values = window.ReadNonBlocking() + if event is 'Exit' or values is None: exit(69) ret, frame = vidFile.read() if not ret: # if out of data stop looping diff --git a/Demo_OpenCV_Webcam.py b/Demo_OpenCV_Webcam.py index ff4448e5..c21eaea0 100644 --- a/Demo_OpenCV_Webcam.py +++ b/Demo_OpenCV_Webcam.py @@ -6,6 +6,7 @@ else: import PySimpleGUI27 as sg import cv2 as cv from PIL import Image +import numpy as np import io from sys import exit as exit @@ -19,39 +20,50 @@ def main(): # define the window layout layout = [[sg.Text('OpenCV Demo', size=(40, 1), justification='center', font='Helvetica 20')], [sg.Image(filename='', key='image')], - [sg.ReadButton('Exit', size=(10, 1), pad=((200, 0), 3), font='Helvetica 14'), + [sg.ReadButton('Record', size=(10, 1), font='Helvetica 14'), + sg.RButton('Stop', size=(10, 1), font='Any 14'), + sg.ReadButton('Exit', size=(10, 1), font='Helvetica 14'), sg.RButton('About', size=(10,1), font='Any 14')]] # create the window and show it without the plot window = sg.Window('Demo Application - OpenCV Integration', location=(800,400)) - window.Layout(layout) - window.ReadNonBlocking() + window.Layout(layout).Finalize() # ---===--- Event LOOP Read and display frames, operate the GUI --- # cap = cv.VideoCapture(0) + recording = False while True: - button, values = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() - if button is 'Exit' or values is None: + if event == 'Exit' or values is None: sys.exit(0) - elif button == 'About': + elif event == 'Record': + recording = True + elif event == 'Stop': + recording = False + img = Image.new('RGB', (640, 480), (255, 255, 255)) + bio = io.BytesIO() # a binary memory resident stream + img.save(bio, format='PNG') # save image as png to it + imgbytes = bio.getvalue() + window.FindElement('image').Update(data=imgbytes) + elif event == 'About': sg.PopupNoWait('Made with PySimpleGUI', 'www.PySimpleGUI.org', 'Check out how the video keeps playing behind this window.', 'I finally figured out how to display frames from a webcam.', 'ENJOY! Go make something really cool with this... please!', keep_on_top=True) + if recording: + ret, frame = cap.read() - ret, frame = cap.read() + gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) - gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) - - # let img be the PIL image - img = Image.fromarray(gray) # create PIL image from frame - bio = io.BytesIO() # a binary memory resident stream - img.save(bio, format= 'PNG') # save image as png to it - imgbytes = bio.getvalue() # this can be used by OpenCV hopefully - window.FindElement('image').Update(data=imgbytes) + # let img be the PIL image + img = Image.fromarray(gray) # create PIL image from frame + bio = io.BytesIO() # a binary memory resident stream + img.save(bio, format= 'PNG') # save image as png to it + imgbytes = bio.getvalue() # this can be used by OpenCV hopefully + window.FindElement('image').Update(data=imgbytes) main() \ No newline at end of file diff --git a/Demo_PDF_Viewer.py b/Demo_PDF_Viewer.py index 5d9b150c..39351a76 100644 --- a/Demo_PDF_Viewer.py +++ b/Demo_PDF_Viewer.py @@ -124,20 +124,20 @@ old_zoom = 0 # used for zoom on/off # the zoom buttons work in on/off mode. while True: - button, value = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() zoom = 0 force_page = False - if button is None and value is None: + if event is None and values is None: break - if button is None: + if event is None: continue - if button in ("Escape:27",): # this spares me a 'Quit' button! + if event in ("Escape:27",): # this spares me a 'Quit' button! break # print("hex(button)", hexlify(button.encode())) - if button[0] == chr(13): # surprise: this is 'Enter'! + if event[0] == chr(13): # surprise: this is 'Enter'! try: - cur_page = int(value[0]) - 1 # check if valid + cur_page = int(values[0]) - 1 # check if valid while cur_page < 0: cur_page += page_count except: @@ -145,17 +145,17 @@ while True: goto.Update(str(cur_page + 1)) # goto.TKStringVar.set(str(cur_page + 1)) - elif button in ("Next", "Next:34", "MouseWheel:Down"): + elif event in ("Next", "Next:34", "MouseWheel:Down"): cur_page += 1 - elif button in ("Prev", "Prior:33", "MouseWheel:Up"): + elif event in ("Prev", "Prior:33", "MouseWheel:Up"): cur_page -= 1 - elif button == "Top-L": + elif event == "Top-L": zoom = 1 - elif button == "Top-R": + elif event == "Top-R": zoom = 2 - elif button == "Bot-L": + elif event == "Bot-L": zoom = 3 - elif button == "Bot-R": + elif event == "Bot-R": zoom = 4 # sanitize page number @@ -169,7 +169,7 @@ while True: zoom = old_zoom = 0 force_page = True - if button in zoom_buttons: + if event in zoom_buttons: if 0 < zoom == old_zoom: zoom = 0 force_page = True @@ -184,6 +184,6 @@ while True: old_zoom = zoom # update page number field - if button in my_keys or not value[0]: + if event in my_keys or not values[0]: goto.Update(str(cur_page + 1)) # goto.TKStringVar.set(str(cur_page + 1)) diff --git a/Demo_PNG_Thumbnail_Viewer.py b/Demo_PNG_Thumbnail_Viewer.py new file mode 100644 index 00000000..197fbb24 --- /dev/null +++ b/Demo_PNG_Thumbnail_Viewer.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +import sys +if sys.version_info[0] >= 3: + import PySimpleGUI as sg +else: + import PySimpleGUI27 as sg +import os +from sys import exit as exit +from PIL import Image +import io +import numpy as np + +thumbnails = {} + +ROWS = 8 +COLUMNS = 8 +sg.SetOptions(border_width=0) +# Get the folder containing the images from the user +# folder = 'A:/TEMP/pdfs' +folder = sg.PopupGetFolder('Image folder to open') +if folder is None: + sg.PopupCancel('Cancelling') + exit(0) +def image_file_to_bytes(filename, size): + try: + image = Image.open(filename) + image.thumbnail(size, Image.ANTIALIAS) + bio = io.BytesIO() # a binary memory resident stream + image.save(bio, format='PNG') # save image as png to it + imgbytes = bio.getvalue() + except: + imgbytes = None + return imgbytes + +def set_image_to_blank(key): + img = Image.new('RGB', (100, 100), (255, 255, 255)) + img.thumbnail((1, 1), Image.ANTIALIAS) + bio = io.BytesIO() + img.save(bio, format='PNG') + imgbytes = bio.getvalue() + window.FindElement(key).Update(image_data=imgbytes) + + + +# get list of PNG files in folder +png_files = [os.path.join(folder, f) for f in os.listdir(folder) if '.png' in f] +filenames_only = [f for f in os.listdir(folder) if '.png' in f] + +if len(png_files) == 0: + sg.Popup('No PNG images in folder') + exit(0) + +# define menu layout +menu = [['&File', ['&Open Folder', 'E&xit']], ['&Help', ['&About',]]] + +buttons = [] +for display_index in range(ROWS): + row = [] + for j in range(COLUMNS): + row.append(sg.RButton('',border_width=0,button_color=sg.COLOR_SYSTEM_DEFAULT, key=(display_index, j))) + buttons.append(row) + +col_buttons = [[]] + +# define layout, show and read the window +col = [[sg.Text(png_files[0], size=(80, 3), key='filename')], + [sg.Image(data=image_file_to_bytes(png_files[0], (500,500)), key='image')],] + +layout = [[sg.Menu(menu)], [sg.Column(buttons), sg.Column([[sg.Slider((len(png_files),0),default_value=0,size=(38,20),orientation='v', key='_slider_', change_submits=True)]]), sg.Column(col)]] +window = sg.Window('Image Browser', + return_keyboard_events=True, + use_default_focus=False ).Layout(layout).Finalize() + +# -------========= Event Loop =========-------- +display_index=0 +while True: + for x in range(ROWS): # update thumbnails + for y in range(COLUMNS): + cur_index = display_index + (x * 4) + y + if cur_index < len(png_files): + filename = png_files[cur_index] + if filename not in thumbnails: + imgbytes = image_file_to_bytes(filename, (100,100)) + thumbnails[filename] = imgbytes + else: + imgbytes = thumbnails[filename] + button_elem = window.FindElement(key=(x,y)) + button_elem.Update(image_data=imgbytes) + else: + set_image_to_blank((x,y)) + + event, values = window.Read() + display_index = values['_slider_'] + # --------------------- Button & Keyboard --------------------- + if event in (None, 'Exit'): + break + elif event in ('MouseWheel:Down', 'Down:40',) and display_index < len(png_files)-1: + display_index += 4 + elif event in ('MouseWheel:Up', 'Up:38',) and display_index > 0: + display_index -= 4 + elif event in ('Prior:33', 'Prev'): + display_index -= 16 + elif event in ('Next:34', 'Next'): + display_index += 16 + + window.FindElement('_slider_').Update(display_index) + # ----------------- Menu choices ----------------- + if event == 'Open Folder': + newfolder = sg.PopupGetFolder('New folder', no_window=True) + if newfolder is None: + continue + folder = newfolder + png_files = [os.path.join(folder, f) for f in os.listdir(folder) if '.png' in f] + filenames_only = [f for f in os.listdir(folder) if '.png' in f] + display_index = 0 + thumbnail = {} + for j in range(ROWS): + for i in range(COLUMNS): + set_image_to_blank((i,j)) + # img = Image.new('RGB', (1,1), (255,255,255)) + # img.thumbnail((1,1), Image.ANTIALIAS) + # bio = io.BytesIO() + # img.save(bio, format='PNG') + # imgbytes = bio.getvalue() + # [window.FindElement((i,j)).Update(image_data=imgbytes) for j in range(ROWS) for i in range(COLUMNS)] + elif event == 'About': + sg.Popup('Demo PNG Viewer Program', 'Please give PySimpleGUI a try!') + elif type(event) is tuple: + x, y = event + image_index = display_index + (x * 4) + y + if image_index < len(png_files): + filename = png_files[image_index] + imgbytes = image_file_to_bytes(filename, (500, 500)) + window.FindElement('image').Update(data=imgbytes) + window.FindElement('filename').Update(filename) + diff --git a/Demo_PNG_Viewer.py b/Demo_PNG_Viewer.py index 75aac14a..807c89e7 100644 --- a/Demo_PNG_Viewer.py +++ b/Demo_PNG_Viewer.py @@ -42,21 +42,21 @@ window = sg.Window('Image Browser', return_keyboard_events=True, location=(0,0), i=0 while True: - button, values = window.Read() + event, values = window.Read() # --------------------- Button & Keyboard --------------------- - if button is None: + if event is None: break - elif button in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34') and i < len(png_files)-1: + elif event in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34') and i < len(png_files)-1: i += 1 - elif button in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33') and i > 0: + elif event in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33') and i > 0: i -= 1 - elif button == 'Exit': + elif event == 'Exit': exit(69) - filename = folder + '/' + values['listbox'][0] if button == 'Read' else png_files[i] + filename = folder + '/' + values['listbox'][0] if event == 'Read' else png_files[i] # ----------------- Menu choices ----------------- - if button == 'Open Folder': + if event == 'Open Folder': newfolder = sg.PopupGetFolder('New folder', no_window=True) if newfolder is None: continue @@ -66,7 +66,7 @@ while True: window.FindElement('listbox').Update(values=filenames_only) window.Refresh() i = 0 - elif button == 'About': + elif event == 'About': sg.Popup('Demo PNG Viewer Program', 'Please give PySimpleGUI a try!') # update window with new image diff --git a/Demo_PSG_SDK_Quick_Ref.py b/Demo_PSG_SDK_Quick_Ref.py index d06cf04e..886579e8 100644 --- a/Demo_PSG_SDK_Quick_Ref.py +++ b/Demo_PSG_SDK_Quick_Ref.py @@ -1,4 +1,9 @@ -import PySimpleGUI as sg +#!/usr/bin/env python +import sys +if sys.version_info[0] >= 3: + import PySimpleGUI as sg +else: + import PySimpleGUI27 as sg desc_text = """ Text( text @@ -20,6 +25,7 @@ Shortcuts: Txt, T desc_inputtext = """ InputText( default_text ='' size=(None, None) + disabled=False auto_size_text=None password_char='' justification=None @@ -55,6 +61,7 @@ desc_inputoptionmenu = """ InputOptionMenu(values default_value=None size=(None, None) + disabled=False auto_size_text=None background_color=None text_color=None @@ -90,6 +97,7 @@ CheckBox( text background_color=None text_color=None change_submits=False + disabled=False key=None pad=None tooltip=None) @@ -101,6 +109,7 @@ desc_radio = """ Radio( text group_id default=False + disabled=False size=(None, None) auto_size_text=None background_color=None @@ -114,6 +123,7 @@ Radio( text desc_spin = """ Spin( values initial_value=None + disabled=False change_submits=False size=(None, None) auto_size_text=None @@ -128,6 +138,7 @@ Spin( values desc_multiline = """ MultiLine( default_text='' enter_submits = False + disabled=False autoscroll=False size=(None,None) auto_size_text=None @@ -157,6 +168,7 @@ Button( button_text='' tooltip=None file_types=(("ALL Files", "*.*"),) initial_folder=None + disabled=False image_filename=None image_size=(None, None) image_subsample=None @@ -264,6 +276,7 @@ Slider( range=(None,None) border_width=None relief=None change_submits=False + disabled=False size=(None, None) font=None background_color=None @@ -277,6 +290,7 @@ Slider( range=(None,None) desc_spin = """ Spin( values initial_value=None + disabled=False change_submits=False size=(None, None) auto_size_text=None @@ -414,7 +428,7 @@ tab_progressbar = [[sg.Column([[sg.Text(desc_progressbar, font=('Consolas 12'))] tab_optionmenu = [[sg.Column([[sg.OptionMenu([1,2,3,4,5], size=(15,1))],[sg.Text(desc_inputoptionmenu, font=('Consolas 12'))]])]] tab_combo = [[sg.Column([[sg.Combo([1,2,3,4,5], size=(15,1))],[sg.Text(desc_inputoptionmenu, font=('Consolas 12'))]])]] tab_frame = [[sg.Column([[sg.Frame('Frame',[[sg.T(' ')]], size=(15,1))],[sg.Text(desc_frame, font=('Consolas 12'))]])]] -tab_column = [[sg.Column([[]],size=(15,1))],[sg.Text(desc_column, font=('Consolas 12'))]] +tab_column = [[sg.Text(desc_column, font=('Consolas 12'))]] tab_graph = [[sg.Text(desc_graph, font=('Consolas 12'))]] tab_tab = [[sg.Text(desc_tab, font=('Consolas 12'))]] tab_tabgroup = [[sg.Text(desc_tabgroup, font=('Consolas 12'))]] @@ -423,16 +437,25 @@ tab_table = [[sg.Text(desc_table, font=('Consolas 12'))]] tab_window = [[sg.Text(desc_window, font=('Consolas 12'))]] layout = [[sg.TabGroup([[sg.Tab('Window',tab_window), - sg.Tab('Text',tab_text), sg.Tab('InputText', tab_input), sg.Tab('Checkbox', tab_checkbox), - sg.Tab('Radio',tab_radio), sg.Tab('Listbox', tab_listbox), sg.Tab('Slider', tab_slider), - sg.Tab('Spinner',tab_spinner), sg.Tab('Multiline', tab_multiline), + sg.Tab('Text',tab_text), + sg.Tab('InputText', tab_input), + sg.Tab('Checkbox', tab_checkbox), + sg.Tab('Radio',tab_radio), + sg.Tab('Listbox', tab_listbox), + sg.Tab('Slider', tab_slider), + sg.Tab('Spinner',tab_spinner), + sg.Tab('Multiline', tab_multiline), sg.Tab('Output', tab_output), sg.Tab('ProgressBar', tab_progressbar), - sg.Tab('OptionMenu', tab_optionmenu), sg.Tab('Frame', tab_frame), - sg.Tab('Combo', tab_combo), sg.Tab('Column', tab_column), - sg.Tab('Graph', tab_graph), sg.Tab('Image', tab_image), + sg.Tab('OptionMenu', tab_optionmenu), + sg.Tab('Combo', tab_combo), + sg.Tab('Image', tab_image), sg.Tab('Table', tab_table), - sg.Tab('Tab', tab_tab), sg.Tab('TabGroup', tab_tabgroup), + sg.Tab('Graph', tab_graph), + sg.Tab('Frame', tab_frame), + sg.Tab('Column', tab_column), + sg.Tab('Tab', tab_tab), + sg.Tab('TabGroup', tab_tabgroup), ]], tab_location='lefttop', title_color='blue', selected_title_color='red')]] # layout = [[sg.Text('The PySimpleGUI SDK Quick Reference Guide', font='Any 15', relief=sg.RELIEF_RAISED)], @@ -444,8 +467,8 @@ window = sg.Window('PySimpleGUI SDK Quick Reference', font='Any 12').Layout(layout) while True: - button, values = window.Read() - if button is None or button == 'Exit': + event, values = window.Read() + if event is None or event == 'Exit': break element = values['_in_'][0] try: diff --git a/Demo_Password_Login.py b/Demo_Password_Login.py index 16a4a840..1f7abfbd 100644 --- a/Demo_Password_Login.py +++ b/Demo_Password_Login.py @@ -30,8 +30,8 @@ def HashGeneratorGUI(): text_justification='r', return_keyboard_events=True, grab_anywhere=False).Layout(layout) while True: - button, values = window.Read() - if button is None: + event, values = window.Read() + if event is None: exit(69) password = values['password'] diff --git a/Demo_Pi_LEDs.py b/Demo_Pi_LEDs.py index bd8991e5..7f0e6623 100644 --- a/Demo_Pi_LEDs.py +++ b/Demo_Pi_LEDs.py @@ -41,13 +41,13 @@ layout = [[sg.T('Raspberry Pi LEDs')], window = sg.Window('Raspberry Pi GUI', grab_anywhere=False).Layout(layout) while True: - button, values = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - if button is 'Switch LED': + if event is 'Switch LED': window.FindElement('output').Update(SwitchLED()) - elif button is 'Flash LED': + elif event is 'Flash LED': window.FindElement('output').Update('LED is Flashing') window.ReadNonBlocking() FlashLED() diff --git a/Demo_Pi_Robotics.py b/Demo_Pi_Robotics.py index 494dd200..511aa66b 100644 --- a/Demo_Pi_Robotics.py +++ b/Demo_Pi_Robotics.py @@ -40,13 +40,13 @@ def RemoteControlExample(): # your program's main loop while (True): # This is the code that reads and updates your window - button, values = window.ReadNonBlocking() - if button is not None: - window.FindElement('status').Update(button) + event, values = window.ReadNonBlocking() + if event is not None: + window.FindElement('status').Update(event) else: window.FindElement('status').Update('') # if user clicked quit button OR closed the form using the X, then break out of loop - if button == 'Quit' or values is None: + if event == 'Quit' or values is None: break window.CloseNonBlocking() @@ -73,13 +73,13 @@ def RemoteControlExample_NoGraphics(): # your program's main loop while (True): # This is the code that reads and updates your window - button, values = window.ReadNonBlocking() - if button is not None: - window.FindElement('status').Update(button) + event, values = window.ReadNonBlocking() + if event is not None: + window.FindElement('status').Update(event) else: window.FindElement('status').Update('') # if user clicked quit button OR closed the form using the X, then break out of loop - if button == 'Quit' or values is None: + if event == 'Quit' or values is None: break window.CloseNonBlocking() diff --git a/Demo_Ping_Line_Graph.py b/Demo_Ping_Line_Graph.py index 2395f885..264cca2c 100644 --- a/Demo_Ping_Line_Graph.py +++ b/Demo_Ping_Line_Graph.py @@ -638,8 +638,8 @@ prev_x, prev_y = canvas_left, canvas_bottom while True: time.sleep(.2) - button, values = window.ReadNonBlocking() - if button == 'Quit' or values is None: + event, values = window.ReadNonBlocking() + if event == 'Quit' or values is None: break if g_response_time is None or prev_response_time == g_response_time: diff --git a/Demo_Pong.py b/Demo_Pong.py index 2e8b5cd2..ec8d1855 100644 --- a/Demo_Pong.py +++ b/Demo_Pong.py @@ -155,19 +155,19 @@ def pong(): bat2.draw() # ------------- Read the form, get keypresses ------------- - button, values = window.ReadNonBlocking() + event, values = window.ReadNonBlocking() # ------------- If quit ------------- - if button is None and values is None or button == 'Quit': + if event is None and values is None or event == 'Quit': exit(69) # ------------- Keypresses ------------- - if button is not None: - if button.startswith('Up'): + if event is not None: + if event.startswith('Up'): bat2.up(2) - elif button.startswith('Down'): + elif event.startswith('Down'): bat2.down(2) - elif button == 'w': + elif event == 'w': bat1.up(1) - elif button == 's': + elif event == 's': bat1.down(1) if ball1.checkwin(): diff --git a/Demo_Popup_Custom.py b/Demo_Popup_Custom.py index eb807938..c1b8c93d 100644 --- a/Demo_Popup_Custom.py +++ b/Demo_Popup_Custom.py @@ -22,8 +22,8 @@ def PopupDropDown(title, text, values): window = sg.Window(title).Layout([[sg.Text(text)], [sg.DropDown(values, key='_drop')], [sg.OK(), sg.Cancel()]]) - button, values = window.Read() - return None if button != 'OK' else values['_drop'] + event, values = window.Read() + return None if event != 'OK' else values['_drop'] # ----------------------- Calling your PopupDropDown function ----------------------- diff --git a/Demo_Popups.py b/Demo_Popups.py index be3455be..90a370ad 100644 --- a/Demo_Popups.py +++ b/Demo_Popups.py @@ -5,6 +5,12 @@ if sys.version_info[0] >= 3: else: import PySimpleGUI27 as sg +from PySimpleGUI import Print as print + + +print('test') +sg.PopupGetFile('Get file', save_as=True,file_types=(("ALL Files", "*.jpg"))) + # Here, have some windows on me.... [sg.PopupNoWait('No-wait Popup', location=(500+100*x,500)) for x in range(10)] diff --git a/Demo_Progress_Meters.py b/Demo_Progress_Meters.py index 0fd70e24..a94bff45 100644 --- a/Demo_Progress_Meters.py +++ b/Demo_Progress_Meters.py @@ -37,16 +37,16 @@ def DemoOneLineProgressMeter(): sg.T('Delay'), sg.In(default_text='10', key='TimeOuter', size=(5,1), do_not_clear=True), sg.T('ms')], [sg.T('Inner Loop Count', size=(15,1), justification='r'), sg.In(default_text='100', size=(5,1), key='CountInner', do_not_clear=True) , sg.T('Delay'), sg.In(default_text='10', key='TimeInner', size=(5,1), do_not_clear=True), sg.T('ms')], - [sg.Button('Show', pad=((0,0), 3), bind_return_key=True), sg.T('me the meters!')] + [sg.RButton('Show', pad=((0,0), 3), bind_return_key=True), sg.T('me the meters!')] ] window = sg.Window('One-Line Progress Meter Demo').Layout(layout) while True: - button, values = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - if button == 'Show': + if event == 'Show': max_outer = int(values['CountOuter']) max_inner = int(values['CountInner']) delay_inner = int(values['TimeInner']) @@ -77,8 +77,8 @@ def CustomMeter(): # loop that would normally do something useful for i in range(10000): # check to see if the cancel button was clicked and exit loop if clicked - button, values = window.ReadNonBlocking() - if button == 'Cancel' or values == None: + event, values = window.ReadNonBlocking() + if event == 'Cancel' or values == None: break # update bar with loop value +1 so that bar eventually reaches the maximum progress_bar.UpdateBar(i+1) diff --git a/Demo_Pyplot_Bar_Chart.py b/Demo_Pyplot_Bar_Chart.py index e60322d1..34265b3d 100644 --- a/Demo_Pyplot_Bar_Chart.py +++ b/Demo_Pyplot_Bar_Chart.py @@ -84,4 +84,4 @@ window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', for fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig) # show it all again and get buttons -button, values = window.Read() +event, values = window.Read() diff --git a/Demo_Pyploy_Bar_Chart2.py b/Demo_Pyploy_Bar_Chart2.py index 4c8e166c..fa02b35f 100644 --- a/Demo_Pyploy_Bar_Chart2.py +++ b/Demo_Pyploy_Bar_Chart2.py @@ -76,4 +76,4 @@ window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', for fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig) # show it all again and get buttons -button, values = window.Read() +event, values = window.Read() diff --git a/Demo_Script_Launcher.py b/Demo_Script_Launcher.py index 9ee52fea..f54edb37 100644 --- a/Demo_Script_Launcher.py +++ b/Demo_Script_Launcher.py @@ -55,18 +55,18 @@ def Launcher2(): # ---===--- Loop taking in user input --- # while True: - (button, value) = window.Read() - if button in ('EXIT', None): + event, values = window.Read() + if event in ('EXIT', None): break # exit button clicked - if button in ('Shortcut 1', 'Fav Program'): + if event in ('Shortcut 1', 'Fav Program'): print('Quickly launch your favorite programs using these shortcuts') print('Or copy files to your github folder. Or anything else you type on the command line') # copyfile(source, dest) - elif button is 'Run': - for index, file in enumerate(value['demolist']): + elif event is 'Run': + for index, file in enumerate(values['demolist']): print('Launching %s'%file) window.Refresh() # make the print appear immediately - if value['wait']: + if values['wait']: execute_command_blocking(LOCATION_OF_YOUR_SCRIPTS + file) else: execute_command_nonblocking(LOCATION_OF_YOUR_SCRIPTS + file) diff --git a/Demo_Script_Parameters.py b/Demo_Script_Parameters.py index 93dfeb6b..81e321d0 100644 --- a/Demo_Script_Parameters.py +++ b/Demo_Script_Parameters.py @@ -19,9 +19,9 @@ stores the result in the variable fname, just like the command line parsing did. ''' if len(sys.argv) == 1: - button, (fname,) = sg.Window('My Script').LayoutAndRead([[sg.T('Document to open')], - [sg.In(), sg.FileBrowse()], - [sg.Open(), sg.Cancel()]]) + event, (fname,) = sg.Window('My Script').LayoutAndRead([[sg.T('Document to open')], + [sg.In(), sg.FileBrowse()], + [sg.Open(), sg.Cancel()]]) else: fname = sys.argv[1] diff --git a/Demo_Spinner_Compound_Element.py b/Demo_Spinner_Compound_Element.py index 219149bd..d93121c3 100644 --- a/Demo_Spinner_Compound_Element.py +++ b/Demo_Spinner_Compound_Element.py @@ -27,11 +27,11 @@ window = sg.Window('Spinner simulation').Layout(layout) # --- Event Loop --- # counter = 0 while True: - button, value = window.Read() + event, values = window.Read() - if button == 'Ok' or button is None: # be nice to your user, always have an exit from your form + if event == 'Ok' or event is None: # be nice to your user, always have an exit from your form break # --- do spinner stuff --- # - counter += 1 if button =='+' else -1 if button == '-' else 0 + counter += 1 if event == '+' else -1 if event == '-' else 0 window.FindElement('spin').Update(counter) diff --git a/Demo_Super_Simple_Form.py b/Demo_Super_Simple_Form.py index df7575a2..43e3d506 100644 --- a/Demo_Super_Simple_Form.py +++ b/Demo_Super_Simple_Form.py @@ -18,6 +18,6 @@ layout = [ ] window = sg.Window('Simple Data Entry Window').Layout(layout) -button, values = window.Read() +event, values = window.Read() -sg.Popup(button, values, values['name'], values['address'], values['phone']) \ No newline at end of file +sg.Popup(event, values, values['name'], values['address'], values['phone']) \ No newline at end of file diff --git a/Demo_Table_CSV.py b/Demo_Table_CSV.py index ae4e9423..986c9d4b 100644 --- a/Demo_Table_CSV.py +++ b/Demo_Table_CSV.py @@ -6,7 +6,6 @@ else: import PySimpleGUI27 as sg import csv - def table_example(): filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files","*.csv"),)) # --- populate table with file contents --- # @@ -29,14 +28,19 @@ def table_example(): sys.exit(69) sg.SetOptions(element_padding=(0, 0)) - col_layout = [[sg.Table(values=data, headings=header_list, max_col_width=25, - auto_size_columns=True, justification='right', size=(None, len(data)))]] + col_layout = [[sg.Table(values=data, + headings=header_list, + max_col_width=25, + auto_size_columns=True, + justification='right', + alternating_row_color='lightblue', + num_rows=len(data))]] canvas_size = (13*10*len(header_list), 600) # estimate canvas size - 13 pixels per char * 10 char per column * num columns layout = [[sg.Column(col_layout, size=canvas_size, scrollable=True)],] window = sg.Window('Table', grab_anywhere=False).Layout(layout) - b, v = window.Read() + event, values = window.Read() sys.exit(69) diff --git a/Demo_Table_Element.py b/Demo_Table_Element.py index 237b575d..caa873ff 100644 --- a/Demo_Table_Element.py +++ b/Demo_Table_Element.py @@ -1,5 +1,8 @@ #!/usr/bin/env python import sys + +from Demo_Turtle import canvas + if sys.version_info[0] >= 3: import PySimpleGUI as sg else: @@ -21,12 +24,16 @@ if filename is not None: sg.SetOptions(element_padding=(0, 0)) -col_layout = [[sg.Table(values=data[1:][:], headings=[data[0][x] for x in range(len(data[0]))], max_col_width=25, - auto_size_columns=True, display_row_numbers=True, justification='right', size=(None, len(data)))]] +headings = [data[0][x] for x in range(len(data[0]))] -layout = [[sg.Column(col_layout, size=(1200,600), scrollable=True)],] +col_layout = [[sg.Table(values=data[1:][:], headings=headings, max_col_width=25, + auto_size_columns=True, display_row_numbers=True, justification='right', num_rows=len(data), alternating_row_color='lightblue')]] + +canvas_size = (13 * 10 * len(headings), 600) # estimate canvas size - 13 pixels per char * 10 per column * num columns + +layout = [[sg.Column(col_layout, size=canvas_size, scrollable=True)],] window = sg.Window('Table', grab_anywhere=False).Layout(layout) -b, v = window.Read() +event, values = window.Read() sys.exit(69) diff --git a/Demo_Table_Pandas.py b/Demo_Table_Pandas.py index 022aa1bf..591ec52b 100644 --- a/Demo_Table_Pandas.py +++ b/Demo_Table_Pandas.py @@ -31,13 +31,13 @@ def table_example(): # sg.SetOptions(element_padding=(0, 0)) col_layout = [[sg.Table(values=data, headings=header_list, display_row_numbers=True, - auto_size_columns=False, size=(None, len(data)))]] + auto_size_columns=False, num_rows=len(data))]] canvas_size = (13*10*len(header_list), 600) # estimate canvas size - 13 pixels per char * 10 per column * num columns layout = [[sg.Column(col_layout, size=canvas_size, scrollable=True)]] window = sg.Window('Table', grab_anywhere=False) - b, v = window.LayoutAndRead(layout) + event, values = window.LayoutAndRead(layout) sys.exit(69) diff --git a/Demo_Table_Simulation.py b/Demo_Table_Simulation.py index 0889ed4f..829fa3f0 100644 --- a/Demo_Table_Simulation.py +++ b/Demo_Table_Simulation.py @@ -37,13 +37,13 @@ def TableSimulation(): window = sg.Window('Table', return_keyboard_events=True, grab_anywhere=False).Layout(layout) while True: - button, values = window.Read() + event, values = window.Read() # --- Process buttons --- # - if button is None or button == 'Exit': + if event is None or event == 'Exit': break - elif button == 'About...': + elif event == 'About...': sg.Popup('Demo of table capabilities') - elif button == 'Open': + elif event == 'Open': filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files","*.csv"),)) # --- populate table with file contents --- # if filename is not None: diff --git a/Demo_Tabs.py b/Demo_Tabs.py index 9a8e30be..296a71cf 100644 --- a/Demo_Tabs.py +++ b/Demo_Tabs.py @@ -44,8 +44,8 @@ window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(la while True: - b, v = window.Read() - sg.PopupNonBlocking(b,v) - print(b,v) - if b is None: # always, always give a way out! + event, values = window.Read() + sg.PopupNonBlocking(event, values) + print(event, values) + if event is None: # always, always give a way out! break diff --git a/Demo_Tabs_Nested.py b/Demo_Tabs_Nested.py index 1c0b87b6..2812ba4f 100644 --- a/Demo_Tabs_Nested.py +++ b/Demo_Tabs_Nested.py @@ -33,7 +33,7 @@ window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(la print('Are there enough tabs for you?') while True: - button, values = window.Read() - print(button,values) - if button is None: # always, always give a way out! + event, values = window.Read() + print(event, values) + if event is None: # always, always give a way out! break \ No newline at end of file diff --git a/Demo_Tabs_Simple.py b/Demo_Tabs_Simple.py index 0e687e31..ad0af921 100644 --- a/Demo_Tabs_Simple.py +++ b/Demo_Tabs_Simple.py @@ -19,8 +19,7 @@ window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(la while True: - b, v = window.Read() - sg.PopupNonBlocking('button = %s'%b,'Values dictionary', v) - print(b,v) - if b is None: # always, always give a way out! + event, values = window.Read() + sg.PopupNonBlocking('button = %s' % event, 'Values dictionary', values) + if event is None: # always, always give a way out! break diff --git a/Demo_Template.py b/Demo_Template.py index 286c6fa4..b2d90243 100644 --- a/Demo_Template.py +++ b/Demo_Template.py @@ -14,7 +14,7 @@ layout = [[ sg.Text('My layout') ], [ sg.Button('Next Window')]] window = sg.Window('My window').Layout(layout) -button, value = window.Read() +event, values = window.Read() # -------------------------------------# @@ -33,7 +33,7 @@ layout = [[ sg.Text('My layout') ], window = sg.Window('My new window').Layout(layout) while True: # Event Loop - button, value = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - print(button, value) \ No newline at end of file + print(event, values) \ No newline at end of file diff --git a/Demo_Tree_Element.py b/Demo_Tree_Element.py index a3faf6ea..acc5dedf 100644 --- a/Demo_Tree_Element.py +++ b/Demo_Tree_Element.py @@ -22,8 +22,10 @@ layout = [[ sg.Text('Tree Test') ], window = sg.Window('Tree Element Test').Layout(layout) +print(treedata) + while True: # Event Loop - button, value = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - print(button, value) + print(event, values) diff --git a/Demo_Turtle.py b/Demo_Turtle.py index 77ebb7d4..4de9b3a8 100644 --- a/Demo_Turtle.py +++ b/Demo_Turtle.py @@ -18,8 +18,9 @@ import turtle layout = [[ sg.Text('My layout') ], - [sg.Canvas(size=(500,500), key='_canvas_')], - [ sg.RButton('F'), sg.RButton('B'), sg.RButton('L'), sg.RButton('R')]] + [sg.Canvas(size=(800,800), key='_canvas_')], + [ sg.RButton('F'), sg.RButton('B'), sg.RButton('L'), sg.RButton('R')], + [sg.RButton('Spiral'), sg.RButton('Inside Out'), sg.RButton('Circles')]] window = sg.Window('My new window').Layout(layout).Finalize() @@ -28,20 +29,56 @@ canvas = window.FindElement('_canvas_').TKCanvas t = turtle.RawTurtle(canvas) t.pencolor("#ff0000") # Red -t.penup() # Regarding one of the comments -t.pendown() # Regarding one of the comments +t.penup() +t.pendown() while True: # Event Loop - button, value = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - if button == 'F': + if event == 'F': t.forward(100) - elif button == 'B': + elif event == 'B': t.back(100) - elif button == 'L': + elif event == 'L': t.left(90) - elif button == 'R': + elif event == 'R': t.right(90) - + elif event == 'Spiral': + canvas.config(bg='light green') + t.color("blue") + def sqrfunc(size): + for i in range(4): + t.fd(size) + t.left(90) + size = size - 5 + sqrfunc(146) + sqrfunc(126) + sqrfunc(106) + sqrfunc(86) + sqrfunc(66) + sqrfunc(46) + sqrfunc(26) + elif event == 'Inside Out': + canvas.config(bg = "light green") + t.color("blue") + def sqrfunc(size): + for i in range(4): + t.fd(size) + t.left(90) + size = size + 5 + sqrfunc(6) + sqrfunc(26) + sqrfunc(46) + sqrfunc(66) + sqrfunc(86) + sqrfunc(106) + sqrfunc(126) + sqrfunc(146) + elif event == 'Circles': + t.speed(0) + for i in range(400): + t.circle(2 * i*.25) + t.circle(-2 * i*.25) + t.left(i) diff --git a/Demo_Window_Disappear.py b/Demo_Window_Disappear.py index 0fbcb9ac..085cab76 100644 --- a/Demo_Window_Disappear.py +++ b/Demo_Window_Disappear.py @@ -11,11 +11,11 @@ layout = [[ sg.Text('My Window') ], window = sg.Window('My window').Layout(layout) while True: - button, value = window.Read() - if button is None: + event, values = window.Read() + if event is None: break - if button == 'Disappear': - window.Disapper() + if event == 'Disappear': + window.Disappear() sg.Popup('Click OK to make window reappear') window.Reappear() diff --git a/Demo_YouTube_Intro.py b/Demo_YouTube_Intro.py index cd61d71a..88c2690f 100644 --- a/Demo_YouTube_Intro.py +++ b/Demo_YouTube_Intro.py @@ -6,6 +6,6 @@ layout = [[sg.Text('What is your name?')], window = sg.Window('Title of Window').Layout(layout) -button, values = window.Read() +event, values = window.Read() sg.Popup('Hello {}'.format(values[0])) \ No newline at end of file diff --git a/Demo_Youtube-dl_Frontend.py b/Demo_Youtube-dl_Frontend.py index 6e0f30e4..1764c51c 100644 --- a/Demo_Youtube-dl_Frontend.py +++ b/Demo_Youtube-dl_Frontend.py @@ -30,11 +30,11 @@ def DownloadSubtitlesGUI(): # ---===--- Loop taking in user input and using it to query HowDoI --- # while True: - (button, gui) = window.Read() - if button in ('EXIT', None): + event, values = window.Read() + if event in ('EXIT', None): break # exit button clicked - link = gui['link'] - if button is 'Get List': + link = values['link'] + if event is 'Get List': print('Getting list of subtitles....') window.Refresh() command = [f'C:/Python/PycharmProjects/GooeyGUI/youtube-dl --list-subs {link}',] @@ -44,8 +44,8 @@ def DownloadSubtitlesGUI(): combobox.Update(values=lang_list) print('Done') - elif button is 'Download': - lang = gui['lang'] + elif event is 'Download': + lang = values['lang'] if lang is '': lang = 'en' print(f'Downloading subtitle for {lang}...') diff --git a/Demo_psutil_Kill_Processes.py b/Demo_psutil_Kill_Processes.py index 9a76562a..42ea9957 100644 --- a/Demo_psutil_Kill_Processes.py +++ b/Demo_psutil_Kill_Processes.py @@ -62,14 +62,14 @@ def main(): # ---------------- main loop ---------------- while (True): # --------- Read and update window -------- - button, values = window.Read() - if 'Mouse' in button or 'Control' in button or 'Shift' in button: + event, values = window.Read() + if 'Mouse' in event or 'Control' in event or 'Shift' in event: continue # --------- Do Button Operations -------- - if values is None or button == 'Exit': + if values is None or event == 'Exit': break - if button == 'Sort by Name': + if event == 'Sort by Name': psutil.cpu_percent(interval=1) procs = psutil.process_iter() all_procs = [[proc.cpu_percent(), proc.name(), proc.pid] for proc in procs] @@ -78,13 +78,16 @@ def main(): for process in sorted_by_cpu_procs: display_list.append('{:5d} {:5.2f} {}\n'.format(process[2], process[0]/10, process[1])) window.FindElement('_processes_').Update(display_list) - elif button == 'Kill': + elif event == 'Kill': processes_to_kill = values['_processes_'] for proc in processes_to_kill: pid = int(proc[0:5]) - if sg.PopupYesNo('About to kill {} {}'.format(pid, proc[13:]), keep_on_top=True) == 'Yes': - kill_proc_tree(pid=pid) - elif button == 'Sort by % CPU': + if sg.PopupYesNo('About to kill {} {}'.format(pid, proc[12:]), keep_on_top=True) == 'Yes': + try: + kill_proc_tree(pid=pid) + except: + sg.PopupAutoClose('Error killing process', auto_close_duration=1) + elif event == 'Sort by % CPU': psutil.cpu_percent(interval=1) procs = psutil.process_iter() all_procs = [[proc.cpu_percent(), proc.name(), proc.pid] for proc in procs] @@ -103,4 +106,5 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() + sys.exit(0) \ No newline at end of file diff --git a/PySimpleGUI27Gen.py b/PySimpleGUI27Gen.py deleted file mode 100644 index 458c3d8f..00000000 --- a/PySimpleGUI27Gen.py +++ /dev/null @@ -1,5099 +0,0 @@ -#!/usr/bin/python3 -from __future__ import print_function -from __future__ import division -from __future__ import unicode_literals -from __future__ import absolute_import -from builtins import super -from builtins import open -from builtins import range -from builtins import int -from builtins import str -from builtins import object -from future import standard_library -standard_library.install_aliases() -import sys -if sys.version_info[0] >= 3: - import tkinter as tk - from tkinter import filedialog - from tkinter.colorchooser import askcolor - from tkinter import ttk - import tkinter.scrolledtext as tkst - import tkinter.font -else: - import tkinter as tk - # import tkinter as tk - import tkinter.filedialog - import tkinter.ttk - import tkinter.colorchooser - import tkinter.font - import tkinter.scrolledtext - -import datetime -import sys -import textwrap -import pickle -import calendar - - -g_time_start = 0 -g_time_end = 0 -g_time_delta = 0 - -import time -def TimerStart(): - global g_time_start - - g_time_start = time.time() - -def TimerStop(): - global g_time_delta, g_time_end - - g_time_end = time.time() - g_time_delta = g_time_end - g_time_start - print(g_time_delta) - -# ----====----====----==== Constants the user CAN safely change ====----====----====----# -DEFAULT_WINDOW_ICON = 'default_icon.ico' -DEFAULT_ELEMENT_SIZE = (45,1) # In CHARACTERS -DEFAULT_BUTTON_ELEMENT_SIZE = (10,1) # In CHARACTERS -DEFAULT_MARGINS = (10,5) # Margins for each LEFT/RIGHT margin is first term -DEFAULT_ELEMENT_PADDING = (5,3) # Padding between elements (row, col) in pixels -DEFAULT_AUTOSIZE_TEXT = True -DEFAULT_AUTOSIZE_BUTTONS = True -DEFAULT_FONT = ("Helvetica", 10) -DEFAULT_TEXT_JUSTIFICATION = 'left' -DEFAULT_BORDER_WIDTH = 1 -DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form -DEFAULT_DEBUG_WINDOW_SIZE = (80,20) -DEFAULT_WINDOW_LOCATION = (None,None) -MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 -DEFAULT_TOOLTIP_TIME = 400 -#################### COLOR STUFF #################### -BLUES = ("#082567","#0A37A3","#00345B") -PURPLES = ("#480656","#4F2398","#380474") -GREENS = ("#01826B","#40A860","#96D2AB", "#00A949","#003532") -YELLOWS = ("#F3FB62", "#F0F595") -TANS = ("#FFF9D5","#F4EFCF","#DDD8BA") -NICE_BUTTON_COLORS = ((GREENS[3], TANS[0]), ('#000000','#FFFFFF'),('#FFFFFF', '#000000'), (YELLOWS[0], PURPLES[1]), - (YELLOWS[0], GREENS[3]), (YELLOWS[0], BLUES[2])) - -COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long -if sys.platform == 'darwin': - DEFAULT_BUTTON_COLOR = COLOR_SYSTEM_DEFAULT # Foreground, Background (None, None) == System Default - OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = COLOR_SYSTEM_DEFAULT # Colors should never be this long -else: - DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default - OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long - -DEFAULT_ERROR_BUTTON_COLOR =("#FFFFFF", "#FF0000") -DEFAULT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None -DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT -DEFAULT_SCROLLBAR_COLOR = None -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember -# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default -# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default -# DEFAULT_PROGRESS_BAR_COLOR = (GREENS[2], GREENS[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[1], BLUES[1]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar -# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar - -# A transparent button is simply one that matches the background -TRANSPARENT_BUTTON = ('#F0F0F0', '#F0F0F0') -#-------------------------------------------------------------------------------- -# Progress Bar Relief Choices -RELIEF_RAISED = 'raised' -RELIEF_SUNKEN = 'sunken' -RELIEF_FLAT = 'flat' -RELIEF_RIDGE = 'ridge' -RELIEF_GROOVE = 'groove' -RELIEF_SOLID = 'solid' - -DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar -DEFAULT_PROGRESS_BAR_SIZE = (25,20) # Size of Progress Bar (characters for length, pixels for width) -DEFAULT_PROGRESS_BAR_BORDER_WIDTH=1 -DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE -PROGRESS_BAR_STYLES = ('default','winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') -DEFAULT_PROGRESS_BAR_STYLE = 'default' -DEFAULT_METER_ORIENTATION = 'Horizontal' -DEFAULT_SLIDER_ORIENTATION = 'vertical' -DEFAULT_SLIDER_BORDER_WIDTH=1 -DEFAULT_SLIDER_RELIEF = tk.FLAT -DEFAULT_FRAME_RELIEF = tk.GROOVE - -DEFAULT_LISTBOX_SELECT_MODE = tk.SINGLE -SELECT_MODE_MULTIPLE = tk.MULTIPLE -LISTBOX_SELECT_MODE_MULTIPLE = 'multiple' -SELECT_MODE_BROWSE = tk.BROWSE -LISTBOX_SELECT_MODE_BROWSE = 'browse' -SELECT_MODE_EXTENDED = tk.EXTENDED -LISTBOX_SELECT_MODE_EXTENDED = 'extended' -SELECT_MODE_SINGLE = tk.SINGLE -LISTBOX_SELECT_MODE_SINGLE = 'single' - -TABLE_SELECT_MODE_NONE = tk.NONE -TABLE_SELECT_MODE_BROWSE = tk.BROWSE -TABLE_SELECT_MODE_EXTENDED = tk.EXTENDED -DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED - -TITLE_LOCATION_TOP = tk.N -TITLE_LOCATION_BOTTOM = tk.S -TITLE_LOCATION_LEFT = tk.W -TITLE_LOCATION_RIGHT = tk.E -TITLE_LOCATION_TOP_LEFT = tk.NW -TITLE_LOCATION_TOP_RIGHT = tk.NE -TITLE_LOCATION_BOTTOM_LEFT = tk.SW -TITLE_LOCATION_BOTTOM_RIGHT = tk.SE - - - - - -# DEFAULT_METER_ORIENTATION = 'Vertical' -# ----====----====----==== Constants the user should NOT f-with ====----====----====----# -ThisRow = 555666777 # magic number - - -# DEFAULT_WINDOW_ICON = '' -MESSAGE_BOX_LINE_WIDTH = 60 - -# a shameful global variable. This represents the top-level window information. Needed because opening a second window is different than opening the first. -class MyWindows(object): - def __init__(self): - self.NumOpenWindows = 0 - self.user_defined_icon = None - - def Decrement(self): - self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 - # print('---- DECREMENTING Num Open Windows = {} ---'.format(self.NumOpenWindows)) - - def Increment(self): - self.NumOpenWindows += 1 - # print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self.NumOpenWindows)) - -_my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows - -# ====================================================================== # -# One-liner functions that are handy as f_ck # -# ====================================================================== # -def RGB(red,green,blue): return '#%02x%02x%02x' % (red,green,blue) - -# ====================================================================== # -# Enums for types # -# ====================================================================== # -# ------------------------- Button types ------------------------- # -#todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality -#uncomment this line and indent to go back to using Enums -# class ButtonType(Enum): -BUTTON_TYPE_BROWSE_FOLDER = 1 -BUTTON_TYPE_BROWSE_FILE = 2 -BUTTON_TYPE_BROWSE_FILES = 21 -BUTTON_TYPE_SAVEAS_FILE = 3 -BUTTON_TYPE_CLOSES_WIN = 5 -BUTTON_TYPE_CLOSES_WIN_ONLY = 6 -BUTTON_TYPE_READ_FORM = 7 -BUTTON_TYPE_REALTIME = 9 -BUTTON_TYPE_CALENDAR_CHOOSER = 30 -BUTTON_TYPE_COLOR_CHOOSER = 40 - -# ------------------------- Element types ------------------------- # -# class ElementType(Enum): -ELEM_TYPE_TEXT = 1 -ELEM_TYPE_INPUT_TEXT = 20 -ELEM_TYPE_INPUT_COMBO = 21 -ELEM_TYPE_INPUT_OPTION_MENU = 22 -ELEM_TYPE_INPUT_RADIO = 5 -ELEM_TYPE_INPUT_MULTILINE = 7 -ELEM_TYPE_INPUT_CHECKBOX = 8 -ELEM_TYPE_INPUT_SPIN = 9 -ELEM_TYPE_BUTTON = 3 -ELEM_TYPE_IMAGE = 30 -ELEM_TYPE_CANVAS = 40 -ELEM_TYPE_FRAME = 41 -ELEM_TYPE_GRAPH = 42 -ELEM_TYPE_TAB = 50 -ELEM_TYPE_TAB_GROUP = 51 -ELEM_TYPE_INPUT_SLIDER = 10 -ELEM_TYPE_INPUT_LISTBOX = 11 -ELEM_TYPE_OUTPUT = 300 -ELEM_TYPE_COLUMN = 555 -ELEM_TYPE_MENUBAR = 600 -ELEM_TYPE_PROGRESS_BAR = 200 -ELEM_TYPE_BLANK = 100 -ELEM_TYPE_TABLE = 700 -ELEM_TYPE_ERROR = 666 - -# ------------------------- Popup Buttons Types ------------------------- # -POPUP_BUTTONS_YES_NO = 1 -POPUP_BUTTONS_CANCELLED = 2 -POPUP_BUTTONS_ERROR = 3 -POPUP_BUTTONS_OK_CANCEL = 4 -POPUP_BUTTONS_OK = 0 -POPUP_BUTTONS_NO_BUTTONS = 5 - - -# ------------------------------------------------------------------------- # -# ToolTip used by the Elements # -# ------------------------------------------------------------------------- # - -class ToolTip(object): - """ Create a tooltip for a given widget - - (inspired by https://stackoverflow.com/a/36221216) - """ - - def __init__(self, widget, text, timeout=DEFAULT_TOOLTIP_TIME): - self.widget = widget - self.text = text - self.timeout = timeout - #self.wraplength = wraplength if wraplength else widget.winfo_screenwidth() // 2 - self.tipwindow = None - self.id = None - self.x = self.y = 0 - self.widget.bind("", self.enter) - self.widget.bind("", self.leave) - self.widget.bind("", self.leave) - - def enter(self, event=None): - self.schedule() - - def leave(self, event=None): - self.unschedule() - self.hidetip() - - def schedule(self): - self.unschedule() - self.id = self.widget.after(self.timeout, self.showtip) - - def unschedule(self): - if self.id: - self.widget.after_cancel(self.id) - self.id = None - - def showtip(self): - if self.tipwindow: - return - x = self.widget.winfo_rootx() + 20 - y = self.widget.winfo_rooty() + self.widget.winfo_height() - 20 - self.tipwindow = tk.Toplevel(self.widget) - self.tipwindow.wm_overrideredirect(True) - self.tipwindow.wm_geometry("+%d+%d" % (x, y)) - label = tkinter.ttk.Label(self.tipwindow, text=self.text, justify=tk.LEFT, - background="#ffffe0", relief=tk.SOLID, borderwidth=1) - label.pack() - - def hidetip(self): - if self.tipwindow: - self.tipwindow.destroy() - self.tipwindow = None - - -# ---------------------------------------------------------------------- # -# Cascading structure.... Objects get larger # -# Button # -# Element # -# Row # -# Form # -# ---------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # -# Element CLASS # -# ------------------------------------------------------------------------- # -class Element(object): - def __init__(self, type, size=(None, None), auto_size_text=None, font=None, background_color=None, text_color=None, key=None, pad=None, tooltip=None): - self.Size = size - self.Type = type - self.AutoSizeText = auto_size_text - self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad - self.Font = font - - self.TKStringVar = None - self.TKIntVar = None - self.TKText = None - self.TKEntry = None - self.TKImage = None - - self.ParentForm=None - self.ParentContainer=None # will be a Form, Column, or Frame element - self.TextInputDefault = None - self.Position = (0,0) # Default position Row 0, Col 0 - self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR - self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR - self.Key = key # dictionary key for return values - self.Tooltip = tooltip - self.TooltipObject = None - - def FindReturnKeyBoundButton(self, form): - for row in form.Rows: - for element in row: - if element.Type == ELEM_TYPE_BUTTON: - if element.BindReturnKey: - return element - if element.Type == ELEM_TYPE_COLUMN: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_FRAME: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_TAB_GROUP: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - if element.Type == ELEM_TYPE_TAB: - rc = self.FindReturnKeyBoundButton(element) - if rc is not None: - return rc - return None - - def TextClickedHandler(self, event): - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.DisplayText - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - - def ReturnKeyHandler(self, event): - MyForm = self.ParentForm - button_element = self.FindReturnKeyBoundButton(MyForm) - if button_element is not None: - button_element.ButtonCallBack() - - - def ListboxSelectHandler(self, event): - MyForm = self.ParentForm - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def ComboboxSelectHandler(self, event): - MyForm = self.ParentForm - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def CheckboxHandler(self): - MyForm = self.ParentForm - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() - - def TabGroupSelectHandler(self, event): - MyForm = self.ParentForm - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() - - def __del__(self): - try: - self.TKStringVar.__del__() - except: - pass - try: - self.TKIntVar.__del__() - except: - pass - try: - self.TKText.__del__() - except: - pass - try: - self.TKEntry.__del__() - except: - pass - -# ---------------------------------------------------------------------- # -# Input Class # -# ---------------------------------------------------------------------- # -class InputText(Element): - def __init__(self, default_text ='', size=(None, None), auto_size_text=None, password_char='', - justification=None, background_color=None, text_color=None, font=None, tooltip=None, do_not_clear=False, key=None, focus=False, pad=None): - ''' - Input a line of text Element - :param default_text: Default value to display - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param password_char: If non-blank, will display this character for every character typed - :param background_color: Color for Element. Text or RGB Hex - ''' - self.DefaultText = default_text - self.PasswordCharacter = password_char - bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - self.Justification = justification - super().__init__(ELEM_TYPE_INPUT_TEXT, size=size, auto_size_text=auto_size_text, background_color=bg, text_color=fg, key=key, pad=pad, font=font, tooltip=tooltip) - - - def Update(self, value=None, disabled=None): - if disabled is True: - self.TKEntry['state'] = 'disabled' - elif disabled is False: - self.TKEntry['state'] = 'normal' - if value is not None: - try: - self.TKStringVar.set(value) - except: pass - self.DefaultText = value - - def Get(self): - return self.TKStringVar.get() - - def __del__(self): - super().__del__() - - -# ------------------------- INPUT TEXT Element lazy functions ------------------------- # -In = InputText -Input = InputText - -# ---------------------------------------------------------------------- # -# Combo # -# ---------------------------------------------------------------------- # -class InputCombo(Element): - def __init__(self, values, default_value=None, size=(None, None), auto_size_text=None, background_color=None, text_color=None, change_submits=False, disabled=False, key=None, pad=None, tooltip=None): - ''' - Input Combo Box Element (also called Dropdown box) - :param values: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - ''' - self.Values = values - self.DefaultValue = default_value - self.ChangeSubmits = change_submits - self.TKCombo = None - self.InitializeAsDisabled = disabled - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__(ELEM_TYPE_INPUT_COMBO, size=size, auto_size_text=auto_size_text, background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip) - - def Update(self, value=None, values=None, set_to_index=None, disabled=None): - if values is not None: - try: - self.TKCombo['values'] = values - self.TKCombo.current(0) - except: pass - self.Values = values - if value is not None: - for index, v in enumerate(self.Values): - if v == value: - try: - self.TKCombo.current(index) - except: pass - self.DefaultValue = value - break - if set_to_index is not None: - try: - self.TKCombo.current(set_to_index) - self.DefaultValue = self.Values[set_to_index] - except: - pass - if disabled == True: - self.TKCombo['state'] = 'disable' - elif disabled == False: - self.TKCombo['state'] = 'enable' - - - def __del__(self): - try: - self.TKCombo.__del__() - except: - pass - super().__del__() - -# ------------------------- INPUT COMBO Element lazy functions ------------------------- # -Combo = InputCombo -DropDown = InputCombo -Drop = InputCombo - -# ---------------------------------------------------------------------- # -# Option Menu # -# ---------------------------------------------------------------------- # -class InputOptionMenu(Element): - def __init__(self, values, default_value=None, size=(None, None), auto_size_text=None, background_color=None, text_color=None, key=None, pad=None, tooltip=None): - ''' - Input Combo Box Element (also called Dropdown box) - :param values: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - ''' - self.Values = values - self.DefaultValue = default_value - self.TKOptionMenu = None - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__(ELEM_TYPE_INPUT_OPTION_MENU, size=size, auto_size_text=auto_size_text, background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip) - - def Update(self, value=None, values=None, disabled=None): - if values is not None: - self.Values = values - if self.Values is not None: - for index, v in enumerate(self.Values): - if v == value: - try: - self.TKStringVar.set(value) - except: pass - self.DefaultValue = value - break - if disabled == True: - self.TKOptionMenu['state'] = 'disabled' - elif disabled == False: - self.TKOptionMenu['state'] = 'normal' - - - def __del__(self): - try: - self.TKOptionMenu.__del__() - except: - pass - super().__del__() - - -# ------------------------- OPTION MENU Element lazy functions ------------------------- # -OptionMenu = InputOptionMenu - -# ---------------------------------------------------------------------- # -# Listbox # -# ---------------------------------------------------------------------- # -class Listbox(Element): - def __init__(self, values, default_values=None, select_mode=None, change_submits=False, bind_return_key=False, size=(None, None), auto_size_text=None, font=None, background_color=None, text_color=None, key=None, pad=None, tooltip=None): - ''' - Listbox Element - :param values: - :param select_mode: SELECT_MODE_BROWSE, SELECT_MODE_EXTENDED, SELECT_MODE_MULTIPLE, SELECT_MODE_SINGLE - :param font: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex ''' - self.Values = values - self.DefaultValues = default_values - self.TKListbox = None - self.ChangeSubmits = change_submits - self.BindReturnKey = bind_return_key - if select_mode == LISTBOX_SELECT_MODE_BROWSE: - self.SelectMode = SELECT_MODE_BROWSE - elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: - self.SelectMode = SELECT_MODE_EXTENDED - elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE: - self.SelectMode = SELECT_MODE_MULTIPLE - elif select_mode == LISTBOX_SELECT_MODE_SINGLE: - self.SelectMode = SELECT_MODE_SINGLE - else: - self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - - super().__init__(ELEM_TYPE_INPUT_LISTBOX, size=size, auto_size_text=auto_size_text, font=font, background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip) - - def Update(self, values=None, disabled=None): - if disabled == True: - self.TKListbox.configure(state='disabled') - elif disabled == False: - self.TKListbox.configure(state='normal') - if values is not None: - self.TKListbox.delete(0, 'end') - for item in values: - self.TKListbox.insert(tk.END, item) - self.TKListbox.selection_set(0, 0) - self.Values = values - - - - def SetValue(self, values): - for index, item in enumerate(self.Values): - try: - if item in values: - self.TKListbox.selection_set(index) - else: - self.TKListbox.selection_clear(index) - except: pass - self.DefaultValues = values - - def __del__(self): - try: - self.TKListBox.__del__() - except: - pass - super().__del__() - - - -# ---------------------------------------------------------------------- # -# Radio # -# ---------------------------------------------------------------------- # -class Radio(Element): - def __init__(self, text, group_id, default=False, size=(None, None), auto_size_text=None, background_color=None, text_color=None, font=None, key=None, pad=None, tooltip=None): - ''' - Radio Button Element - :param text: - :param group_id: - :param default: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - :param font: - ''' - self.InitialState = default - self.Text = text - self.TKRadio = None - self.GroupID = group_id - self.Value = None - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - - super().__init__(ELEM_TYPE_INPUT_RADIO, size=size, auto_size_text=auto_size_text, font=font, background_color=background_color, text_color=self.TextColor, key=key, pad=pad, tooltip=tooltip) - - def Update(self, value=None, disabled=None): - location = EncodeRadioRowCol(self.Position[0], self.Position[1]) - if value is not None: - try: - self.TKIntVar.set(location) - except: pass - self.InitialState = value - if disabled == True: - self.TKRadio['state'] = 'disabled' - elif disabled == False: - self.TKRadio['state'] = 'normal' - - def __del__(self): - try: - self.TKRadio.__del__() - except: - pass - super().__del__() - -# ---------------------------------------------------------------------- # -# Checkbox # -# ---------------------------------------------------------------------- # -class Checkbox(Element): - def __init__(self, text, default=False, size=(None, None), auto_size_text=None, font=None, background_color=None, text_color=None, change_submits=False, key=None, pad=None, tooltip=None): - ''' - Check Box Element - :param text: - :param default: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - :param font: - ''' - self.Text = text - self.InitialState = default - self.Value = None - self.TKCheckbutton = None - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.ChangeSubmits = change_submits - - super().__init__(ELEM_TYPE_INPUT_CHECKBOX, size=size, auto_size_text=auto_size_text, font=font, - background_color=background_color, text_color=self.TextColor, key=key, pad=pad, tooltip=tooltip) - - def Get(self): - return self.TKIntVar.get() - - def Update(self, value=None, disabled=None): - if value is not None: - try: - self.TKIntVar.set(value) - self.InitialState = value - except: pass - if disabled == True: - self.TKCheckbutton.configure(state='disabled') - elif disabled == False: - self.TKCheckbutton.configure(state='normal') - - - def __del__(self): - super().__del__() - - -# ------------------------- CHECKBOX Element lazy functions ------------------------- # -CB = Checkbox -CBox = Checkbox -Check = Checkbox - - -# ---------------------------------------------------------------------- # -# Spin # -# ---------------------------------------------------------------------- # - -class Spin(Element): - # Values = None - # TKSpinBox = None - def __init__(self, values, initial_value=None, change_submits=False, size=(None, None), auto_size_text=None, font=None, background_color=None, text_color=None, key=None, pad=None, tooltip=None): - ''' - Spin Box Element - :param values: - :param initial_value: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - :param font: - ''' - self.Values = values - self.DefaultValue = initial_value - self.ChangeSubmits = change_submits - self.TKSpinBox = None - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__(ELEM_TYPE_INPUT_SPIN, size, auto_size_text, font=font,background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip) - return - - def Update(self, value=None, values=None, disabled=None): - if values != None: - old_value = self.TKStringVar.get() - self.Values = values - self.TKSpinBox.configure(values=values) - self.TKStringVar.set(old_value) - if value is not None: - try: - self.TKStringVar.set(value) - except: pass - self.DefaultValue = value - if disabled == True: - self.TKSpinBox.configure(state='disabled') - elif disabled == False: - self.TKSpinBox.configure(state='normal') - - - def SpinChangedHandler(self, event): - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def __del__(self): - try: - self.TKSpinBox.__del__() - except: - pass - super().__del__() - -# ---------------------------------------------------------------------- # -# Multiline # -# ---------------------------------------------------------------------- # -class Multiline(Element): - def __init__(self, default_text='', enter_submits = False, autoscroll=False, size=(None, None), auto_size_text=None, background_color=None, text_color=None, do_not_clear=False, key=None, focus=False, pad=None, tooltip=None): - ''' - Input Multi-line Element - :param default_text: - :param enter_submits: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param background_color: Color for Element. Text or RGB Hex - ''' - self.DefaultText = default_text - self.EnterSubmits = enter_submits - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - self.Focus = focus - self.do_not_clear = do_not_clear - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - self.Autoscroll = autoscroll - - super().__init__(ELEM_TYPE_INPUT_MULTILINE, size=size, auto_size_text=auto_size_text, background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip) - return - - def Update(self, value=None, disabled=None, append=False): - if value is not None: - try: - if not append: - self.TKText.delete('1.0', tk.END) - self.TKText.insert(tk.END, value) - except: pass - self.DefaultText = value - if self.Autoscroll: - self.TKText.see(tk.END) - if disabled == True: - self.TKText.configure(state='disabled') - elif disabled == False: - self.TKText.configure(state='normal') - - def Get(self): - return self.TKText.get(1.0, tk.END) - - - def __del__(self): - super().__del__() - -# ---------------------------------------------------------------------- # -# Text # -# ---------------------------------------------------------------------- # -class Text(Element): - def __init__(self, text, size=(None, None), auto_size_text=None, click_submits=None, relief=None, font=None, text_color=None, background_color=None,justification=None, pad=None, key=None, tooltip=None): - ''' - Text Element - Displays text in your form. Can be updated in non-blocking forms - :param text: The text to display - :param size: Size of Element in Characters - :param auto_size_text: True if the field should shrink to fit the text - :param font: Font name and size ("name", size) - :param text_color: Text Color name or RGB hex value '#RRGGBB' - :param background_color: Background color for text (name or RGB Hex) - :param justification: 'left', 'right', 'center' - ''' - self.DisplayText = text - self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.Justification = justification - self.Relief = relief - self.ClickSubmits = click_submits - if background_color is None: - bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - else: - bg = background_color - super().__init__(ELEM_TYPE_TEXT, size, auto_size_text, background_color=bg, font=font if font else DEFAULT_FONT, text_color=self.TextColor, pad=pad, key=key, tooltip=tooltip) - return - - def Update(self, value = None, background_color=None, text_color=None, font=None): - if value is not None: - self.DisplayText=value - stringvar = self.TKStringVar - stringvar.set(value) - if background_color is not None: - self.TKText.configure(background=background_color) - if text_color is not None: - self.TKText.configure(fg=text_color) - if font is not None: - self.TKText.configure(font=font) - - - def __del__(self): - super().__del__() - - -# ------------------------- Text Element lazy functions ------------------------- # -Txt = Text -T = Text - - -# ---------------------------------------------------------------------- # -# TKProgressBar # -# Emulate the TK ProgressBar using canvas and rectangles -# ---------------------------------------------------------------------- # - -class TKProgressBar(object): - def __init__(self, root, max, length=400, width=DEFAULT_PROGRESS_BAR_SIZE[1], style=DEFAULT_PROGRESS_BAR_STYLE, relief=DEFAULT_PROGRESS_BAR_RELIEF, border_width=DEFAULT_PROGRESS_BAR_BORDER_WIDTH, orientation='horizontal', BarColor=(None,None)): - self.Length = length - self.Width = width - self.Max = max - self.Orientation = orientation - self.Count = None - self.PriorCount = 0 - - if orientation[0].lower() == 'h': - s = tkinter.ttk.Style() - s.theme_use(style) - if BarColor != COLOR_SYSTEM_DEFAULT: - s.configure(str(length)+str(width)+"my.Horizontal.TProgressbar", background=BarColor[0], troughcolor=BarColor[1], troughrelief=relief, borderwidth=border_width, thickness=width) - else: - s.configure(str(length)+str(width)+"my.Horizontal.TProgressbar", troughrelief=relief, borderwidth=border_width, thickness=width) - - self.TKProgressBarForReal = tkinter.ttk.Progressbar(root, maximum=self.Max, style=str(length)+str(width)+'my.Horizontal.TProgressbar', length=length, orient=tk.HORIZONTAL, mode='determinate') - else: - s = tkinter.ttk.Style() - s.theme_use(style) - if BarColor != COLOR_SYSTEM_DEFAULT: - s.configure(str(length)+str(width)+"my.Vertical.TProgressbar", background=BarColor[0], troughcolor=BarColor[1], troughrelief=relief, borderwidth=border_width, thickness=width) - else: - s.configure(str(length)+str(width)+"my.Vertical.TProgressbar", troughrelief=relief, borderwidth=border_width, thickness=width) - self.TKProgressBarForReal = tkinter.ttk.Progressbar(root, maximum=self.Max, style=str(length)+str(width)+'my.Vertical.TProgressbar', length=length, orient=tk.VERTICAL, mode='determinate') - - def Update(self, count=None, max=None): - if max is not None: - self.Max = max - try: - self.TKProgressBarForReal.config(maximum=max) - except: - return False - if count is not None and count > self.Max: return False - if count is not None: - try: - self.TKProgressBarForReal['value'] = count - except: return False - return True - - def __del__(self): - try: - self.TKProgressBarForReal.__del__() - except: - pass - -# ---------------------------------------------------------------------- # -# TKOutput # -# New Type of TK Widget that's a Text Widget in disguise # -# Note that it's inherited from the TKFrame class so that the # -# Scroll bar will span the length of the frame # -# ---------------------------------------------------------------------- # -class TKOutput(tk.Frame): - def __init__(self, parent, width, height, bd, background_color=None, text_color=None, font=None, pad=None): - frame = tk.Frame(parent) - tk.Frame.__init__(self, frame) - self.output = tk.Text(frame, width=width, height=height, bd=bd, font=font) - if background_color and background_color != COLOR_SYSTEM_DEFAULT: - self.output.configure(background=background_color) - frame.configure(background=background_color) - if text_color and text_color != COLOR_SYSTEM_DEFAULT: - self.output.configure(fg=text_color) - self.vsb = tk.Scrollbar(frame, orient="vertical", command=self.output.yview) - self.output.configure(yscrollcommand=self.vsb.set) - self.output.pack(side="left", fill="both", expand=True) - self.vsb.pack(side="left", fill="y", expand=False) - frame.pack(side="left", padx=pad[0], pady=pad[1], expand=True, fill='y') - self.previous_stdout = sys.stdout - self.previous_stderr = sys.stderr - - sys.stdout = self - sys.stderr = self - self.pack() - - def write(self, txt): - try: - self.output.insert(tk.END, str(txt)) - self.output.see(tk.END) - except: - pass - - def Close(self): - sys.stdout = self.previous_stdout - sys.stderr = self.previous_stderr - - def flush(self): - sys.stdout = self.previous_stdout - sys.stderr = self.previous_stderr - - def __del__(self): - sys.stdout = self.previous_stdout - sys.stderr = self.previous_stderr - -# ---------------------------------------------------------------------- # -# Output # -# Routes stdout, stderr to a scrolled window # -# ---------------------------------------------------------------------- # -class Output(Element): - def __init__(self, size=(None, None), background_color=None, text_color=None, pad=None, font=None, tooltip=None, key=None): - ''' - Output Element - reroutes stdout, stderr to this window - :param size: Size of field in characters - :param background_color: Color for Element. Text or RGB Hex - ''' - self._TKOut = None - bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR - fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR - - super().__init__(ELEM_TYPE_OUTPUT, size=size, background_color=bg, text_color=fg, pad=pad, font=font, tooltip=tooltip, key=key) - - - @property - def TKOut(self): - if self._TKOut is None: - print('*** Did you forget to call Finalize()? Your code should look something like: ***') - print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') - return self._TKOut - - - def __del__(self): - try: - self._TKOut.__del__() - except: - pass - super().__del__() - -# ---------------------------------------------------------------------- # -# Button Class # -# ---------------------------------------------------------------------- # -class Button(Element): - def __init__(self, button_text='', button_type=BUTTON_TYPE_CLOSES_WIN, target=(None, None), tooltip=None, file_types=(("ALL Files", "*.*"),), initial_folder=None, image_filename=None, image_size=(None, None), image_subsample=None, border_width=None, size=(None, None), auto_size_button=None, button_color=None, default_value = None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - ''' - Button Element - Specifies all types of buttons - :param button_type: - :param target: - :param button_text: - :param file_types: - :param image_filename: - :param image_size: - :param image_subsample: - :param border_width: - :param size: Size of field in characters - :param auto_size_button: - :param button_color: - :param font: - ''' - self.AutoSizeButton = auto_size_button - self.BType = button_type - self.FileTypes = file_types - self.TKButton = None - self.Target = target - self.ButtonText = button_text - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.ImageFilename = image_filename - self.ImageSize = image_size - self.ImageSubsample = image_subsample - self.UserData = None - self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH - self.BindReturnKey = bind_return_key - self.Focus = focus - self.TKCal = None - self.DefaultValue = default_value - self.InitialFolder = initial_folder - - super().__init__(ELEM_TYPE_BUTTON, size=size, font=font, pad=pad, key=key, tooltip=tooltip) - return - - # Realtime button release callback - def ButtonReleaseCallBack(self, parm): - self.LastButtonClickedWasRealtime = False - self.ParentForm.LastButtonClicked = None - - # Realtime button callback - def ButtonPressCallBack(self, parm): - self.ParentForm.LastButtonClickedWasRealtime = True - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - - # ------- Button Callback ------- # - def ButtonCallBack(self): - global _my_windows - # print(f'Parent = {self.ParentForm} Position = {self.Position}') - # Buttons modify targets or return from the form - # If modifying target, get the element object at the target and modify its StrVar - target = self.Target - target_element = None - if target[0] == ThisRow: - target = [self.Position[0], target[1]] - if target[1] < 0: - target[1] = self.Position[1] + target[1] - strvar = None - if target == (None, None): - strvar = self.TKStringVar - else: - if not isinstance(target, str): - if target[0] < 0: - target = [self.Position[0] + target[0], target[1]] - target_element = self.ParentContainer._GetElementAtLocation(target) - else: - target_element = self.ParentForm.FindElement(target) - try: - strvar = target_element.TKStringVar - except: pass - filetypes = [] if self.FileTypes is None else self.FileTypes - if self.BType == BUTTON_TYPE_BROWSE_FOLDER: - folder_name = tk.filedialog.askdirectory(initialdir=self.InitialFolder) # show the 'get folder' dialog box - if folder_name != '': - try: - strvar.set(folder_name) - self.TKStringVar.set(folder_name) - except: pass - elif self.BType == BUTTON_TYPE_BROWSE_FILE: - file_name = tk.filedialog.askopenfilename(filetypes=filetypes, initialdir=self.InitialFolder) # show the 'get file' dialog box - if file_name != '': - strvar.set(file_name) - self.TKStringVar.set(file_name) - elif self.BType == BUTTON_TYPE_COLOR_CHOOSER: - color = tk.colorchooser.askcolor() # show the 'get file' dialog box - color = color[1] # save only the #RRGGBB portion - strvar.set(color) - self.TKStringVar.set(color) - elif self.BType == BUTTON_TYPE_BROWSE_FILES: - file_name = tk.filedialog.askopenfilenames(filetypes=filetypes, initialdir=self.InitialFolder) - if file_name != '': - file_name = ';'.join(file_name) - strvar.set(file_name) - self.TKStringVar.set(file_name) - elif self.BType == BUTTON_TYPE_SAVEAS_FILE: - file_name = tk.filedialog.asksaveasfilename(filetypes=filetypes, initialdir=self.InitialFolder) # show the 'get file' dialog box - if file_name != '': - strvar.set(file_name) - self.TKStringVar.set(file_name) - elif self.BType == BUTTON_TYPE_CLOSES_WIN: # this is a return type button so GET RESULTS and destroy window - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = False - # if the form is tabbed, must collect all form's results and destroy all forms - self.ParentForm._Close() - self.ParentForm.TKroot.quit() - if self.ParentForm.NonBlocking: - self.ParentForm.TKroot.destroy() - _my_windows.Decrement() - elif self.BType == BUTTON_TYPE_READ_FORM: # LEAVE THE WINDOW OPEN!! DO NOT CLOSE - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # this is a return type button so GET RESULTS and destroy window - # if the form is tabbed, must collect all form's results and destroy all forms - self.ParentForm._Close() - if self.ParentForm.NonBlocking: - self.ParentForm.TKroot.destroy() - _my_windows.Decrement() - elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window - root = tk.Toplevel() - root.title('Calendar Chooser') - self.TKCal = TKCalendar(master=root, firstweekday=calendar.SUNDAY, target_element=target_element) - self.TKCal.pack(expand=1, fill='both') - # self.ParentForm.TKRroot.mainloop() - root.update() - # root.mainloop() - # root.update() - # strvar.set(ttkcal.selection) - - return - - def Update(self, value=None, text=None, button_color=(None, None), disabled=None): - try: - if text is not None: - self.TKButton.configure(text=text) - self.ButtonText = text - if button_color != (None, None): - self.TKButton.config(foreground=button_color[0], background=button_color[1]) - except: - return - if value is not None: - self.DefaultValue = value - if disabled == True: - self.TKButton['state'] = 'disabled' - elif disabled == False: - self.TKButton['state'] = 'normal' - - def GetText(self): - return self.ButtonText - - def __del__(self): - try: - self.TKButton.__del__() - except: - pass - super().__del__() - - -# ---------------------------------------------------------------------- # -# ProgreessBar # -# ---------------------------------------------------------------------- # -class ProgressBar(Element): - def __init__(self, max_value, orientation=None, size=(None, None), auto_size_text=None, bar_color=(None, None), style=None, border_width=None, relief=None, key=None, pad=None): - ''' - Progress Bar Element - :param max_value: - :param orientation: - :param size: Size of field in characters - :param auto_size_text: True if should shrink field to fit the default text - :param bar_color: - :param style: - :param border_width: - :param relief: - ''' - self.MaxValue = max_value - self.TKProgressBar = None - self.Cancelled = False - self.NotRunning = True - self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION - self.BarColor = bar_color - self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE - self.BorderWidth = border_width if border_width else DEFAULT_PROGRESS_BAR_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF - self.BarExpired = False - super().__init__(ELEM_TYPE_PROGRESS_BAR, size=size, auto_size_text=auto_size_text, key=key, pad=pad) - return - - # returns False if update failed - def UpdateBar(self, current_count, max=None): - if self.ParentForm.TKrootDestroyed: - return False - self.TKProgressBar.Update(current_count, max=max) - try: - self.ParentForm.TKroot.update() - except: - _my_windows.Decrement() - return False - return True - - def __del__(self): - try: - self.TKProgressBar.__del__() - except: - pass - super().__del__() - -# ---------------------------------------------------------------------- # -# Image # -# ---------------------------------------------------------------------- # -class Image(Element): - def __init__(self, filename=None, data=None, size=(None, None), pad=None, key=None, tooltip=None): - ''' - Image Element - :param filename: - :param size: Size of field in characters - ''' - self.Filename = filename - self.Data = data - self.tktext_label = None - if data is None and filename is None: - print('* Warning... no image specified in Image Element! *') - super().__init__(ELEM_TYPE_IMAGE, size=size, pad=pad, key=key, tooltip=tooltip) - return - - def Update(self, filename=None, data=None): - if filename is not None: - image = tk.PhotoImage(file=filename) - elif data is not None: - if type(data) is bytes: - image = tk.PhotoImage(data=data) - else: - image = data - else: return - width, height = image.width(), image.height() - self.tktext_label.configure(image=image, width=width, height=height) - # self.tktext_label.configure(image=image) - self.tktext_label.image = image - - def __del__(self): - super().__del__() - - -# ---------------------------------------------------------------------- # -# Canvas # -# ---------------------------------------------------------------------- # -class Canvas(Element): - def __init__(self, canvas=None, background_color=None, size=(None, None), pad=None, key=None, tooltip=None): - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self._TKCanvas = canvas - - super().__init__(ELEM_TYPE_CANVAS, background_color=background_color, size=size, pad=pad, key=key, tooltip=tooltip) - return - - @property - def TKCanvas(self): - if self._TKCanvas is None: - print('*** Did you forget to call Finalize()? Your code should look something like: ***') - print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') - return self._TKCanvas - - - def __del__(self): - super().__del__() - - - - -# ---------------------------------------------------------------------- # -# Graph # -# ---------------------------------------------------------------------- # -class Graph(Element): - def __init__(self, canvas_size, graph_bottom_left, graph_top_right, background_color=None, pad=None, key=None, tooltip=None): - - self.CanvasSize = canvas_size - self.BottomLeft = graph_bottom_left - self.TopRight = graph_top_right - self._TKCanvas = None - self._TKCanvas2 = None - - super().__init__(ELEM_TYPE_GRAPH, background_color=background_color, size=canvas_size, pad=pad, key=key, tooltip=tooltip) - return - - def _convert_xy_to_canvas_xy(self, x_in, y_in): - scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0]) - scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1]) - new_x = 0 + scale_x * (x_in - self.BottomLeft[0]) - new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1]) - return new_x, new_y - - def DrawLine(self, point_from, point_to, color='black', width=1): - converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1]) - converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_line(converted_point_from, converted_point_to, width=width, fill=color) - - def DrawPoint(self, point, size=2, color='black'): - converted_point = self._convert_xy_to_canvas_xy(point[0], point[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval(converted_point[0]-size, converted_point[1]-size, converted_point[0]+size, converted_point[1]+size, fill=color, outline=color ) - - def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'): - converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval(converted_point[0]-radius, converted_point[1]-radius, converted_point[0]+radius, converted_point[1]+radius, fill=fill_color, outline=line_color) - - def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1]) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0],bottom_right[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_oval(converted_top_left[0], converted_top_left[1], converted_bottom_right[0], converted_bottom_right[1], fill=fill_color, outline=line_color) - - - def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color=None): - converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1] ) - converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - return self._TKCanvas2.create_rectangle(converted_top_left[0], converted_top_left[1], converted_bottom_right[0], converted_bottom_right[1], fill=fill_color, outline=line_color) - - def DrawText(self, text, location, color='black', font=None, angle=0): - converted_point = self._convert_xy_to_canvas_xy(location[0], location[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - text_id = self._TKCanvas2.create_text(converted_point[0], converted_point[1], text=text, font=font, fill=color, angle=angle) - return text_id - - - def Erase(self): - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self._TKCanvas2.delete('all') - - def Update(self, background_color): - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self._TKCanvas2.configure(background=background_color) - - def Move(self, x_direction, y_direction): - zero_converted = self._convert_xy_to_canvas_xy(0,0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0]-zero_converted[0], shift_converted[1]-zero_converted[1]) - if self._TKCanvas2 is None: - print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***') - print('Call Window.Finalize() prior to this operation') - return None - self._TKCanvas2.move('all', shift_amount[0], shift_amount[1]) - - def MoveFigure(self, figure, x_direction, y_direction): - zero_converted = self._convert_xy_to_canvas_xy(0,0) - shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction) - shift_amount = (shift_converted[0]-zero_converted[0], shift_converted[1]-zero_converted[1]) - if figure is None: - print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***') - print('Call Window.Finalize() prior to all graph operations') - return None - self._TKCanvas2.move(figure, shift_amount[0], shift_amount[1]) - - @property - def TKCanvas(self): - if self._TKCanvas2 is None: - print('*** Did you forget to call Finalize()? Your code should look something like: ***') - print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***') - return self._TKCanvas2 - - def __del__(self): - super().__del__() - - -# ---------------------------------------------------------------------- # -# Frame # -# ---------------------------------------------------------------------- # -class Frame(Element): - def __init__(self, title, layout, title_color=None, background_color=None, title_location=None , relief=DEFAULT_FRAME_RELIEF, size=(None, None), font=None, pad=None, border_width=None, key=None, tooltip=None): - - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - # self.ParentForm = None - self.TKFrame = None - self.Title = title - self.Relief = relief - self.TitleLocation = title_location - self.BorderWidth = border_width - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - - self.Layout(layout) - - super().__init__(ELEM_TYPE_FRAME, background_color=background_color, text_color=title_color, size=size, font=font, pad=pad, key=key, tooltip=tooltip) - return - - def AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num,col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - - def __del__(self): - for row in self.Rows: - for element in row: - element.__del__() - super().__del__() - - - -# ---------------------------------------------------------------------- # -# Tab # -# ---------------------------------------------------------------------- # -class Tab(Element): - def __init__(self, title, layout, title_color=None, background_color=None, font=None, pad=None, border_width=None, key=None, tooltip=None): - - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKFrame = None - self.Title = title - self.BorderWidth = border_width - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - - self.Layout(layout) - - super().__init__(ELEM_TYPE_TAB, background_color=background_color, text_color=title_color, font=font, pad=pad, key=key, tooltip=tooltip) - return - - def AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num,col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - - def __del__(self): - for row in self.Rows: - for element in row: - element.__del__() - super().__del__() - - - -# ---------------------------------------------------------------------- # -# TabGroup # -# ---------------------------------------------------------------------- # -class TabGroup(Element): - def __init__(self, layout, title_color=None, background_color=None, font=None, change_submits=False, pad=None, border_width=None, key=None, tooltip=None): - - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - self.TKNotebook = None - self.BorderWidth = border_width - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.ChangeSubmits = change_submits - - self.Layout(layout) - - super().__init__(ELEM_TYPE_TAB_GROUP, background_color=background_color, text_color=title_color, font=font, pad=pad, key=key, tooltip=tooltip) - return - - def AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num,col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def FindKeyFromTabName(self, tab_name): - for row in self.Rows: - for element in row: - if element.Title == tab_name: - return element.Key - return None - - def __del__(self): - for row in self.Rows: - for element in row: - element.__del__() - super().__del__() - - -# ---------------------------------------------------------------------- # -# Slider # -# ---------------------------------------------------------------------- # -class Slider(Element): - def __init__(self, range=(None,None), default_value=None, resolution=None, orientation=None, border_width=None, relief=None, change_submits=False, size=(None, None), font=None, background_color=None, text_color=None, key=None, pad=None, tooltip=None): - ''' - Slider - :param range: - :param default_value: - :param resolution: - :param orientation: - :param border_width: - :param relief: - :param change_submits: - :param scale: - :param size: - :param font: - :param background_color: - :param text_color: - :param key: - :param pad: - ''' - self.TKScale = None - self.Range = (1,10) if range == (None, None) else range - self.DefaultValue = self.Range[0] if default_value is None else default_value - self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION - self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH - self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF - self.Resolution = 1 if resolution is None else resolution - self.ChangeSubmits = change_submits - - super().__init__(ELEM_TYPE_INPUT_SLIDER, size=size, font=font, background_color=background_color, text_color=text_color, key=key, pad=pad, tooltip=tooltip) - return - - def Update(self, value=None, range=(None, None), disabled=None): - if value is not None: - try: - self.TKIntVar.set(value) - if range != (None, None): - self.TKScale.config(from_ = range[0], to_ = range[1]) - except: pass - self.DefaultValue = value - if disabled == True: - self.TKScale['state'] = 'disabled' - elif disabled == False: - self.TKScale['state'] = 'normal' - - def SliderChangedHandler(self, event): - # first, get the results table built - # modify the Results table in the parent FlexForm object - if self.Key is not None: - self.ParentForm.LastButtonClicked = self.Key - else: - self.ParentForm.LastButtonClicked = '' - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def __del__(self): - super().__del__() - - -# ---------------------------------------------------------------------- # -# TkScrollableFrame (Used by Column) # -# ---------------------------------------------------------------------- # -class TkScrollableFrame(tk.Frame): - def __init__(self, master, **kwargs): - tk.Frame.__init__(self, master, **kwargs) - - # create a canvas object and a vertical scrollbar for scrolling it - self.vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL) - self.vscrollbar.pack(side='right', fill="y", expand="false") - - self.hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL) - self.hscrollbar.pack(side='bottom', fill="x", expand="false") - - self.canvas = tk.Canvas(self, yscrollcommand=self.vscrollbar.set, xscrollcommand=self.hscrollbar.set) - self.canvas.pack(side="left", fill="both", expand=True) - - self.vscrollbar.config(command=self.canvas.yview) - self.hscrollbar.config(command=self.canvas.xview) - - # reset the view - self.canvas.xview_moveto(0) - self.canvas.yview_moveto(0) - - # create a frame inside the canvas which will be scrolled with it - self.TKFrame = tk.Frame(self.canvas, **kwargs) - self.frame_id = self.canvas.create_window(0, 0, window=self.TKFrame, anchor="nw") - self.canvas.config(borderwidth=0, highlightthickness=0) - self.TKFrame.config(borderwidth=0, highlightthickness=0) - self.config(borderwidth=0, highlightthickness=0) - - self.bind('', self.set_scrollregion) - - self.bind_mouse_scroll(self.canvas, self.yscroll) - self.bind_mouse_scroll(self.hscrollbar, self.xscroll) - self.bind_mouse_scroll(self.vscrollbar, self.yscroll) - - def resize_frame(self, e): - self.canvas.itemconfig(self.frame_id, height=e.height, width=e.width) - - def yscroll(self, event): - if event.num == 5 or event.delta < 0: - self.canvas.yview_scroll(1, "unit") - elif event.num == 4 or event.delta > 0: - self.canvas.yview_scroll(-1, "unit") - - def xscroll(self, event): - if event.num == 5 or event.delta < 0: - self.canvas.xview_scroll(1, "unit") - elif event.num == 4 or event.delta > 0: - self.canvas.xview_scroll(-1, "unit") - - def bind_mouse_scroll(self, parent, mode): - # ~~ Windows only - parent.bind("", mode) - # ~~ Unix only - parent.bind("", mode) - parent.bind("", mode) - - def set_scrollregion(self, event=None): - """ Set the scroll region on the canvas""" - self.canvas.configure(scrollregion=self.canvas.bbox('all')) - - -# ---------------------------------------------------------------------- # -# Column # -# ---------------------------------------------------------------------- # -class Column(Element): - def __init__(self, layout, background_color = None, size=(None, None), pad=None, scrollable=False, key=None): - self.UseDictionary = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.ParentWindow = None - self.Rows = [] - # self.ParentForm = None - self.TKFrame = None - self.Scrollable = scrollable - bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - - self.Layout(layout) - - super().__init__(ELEM_TYPE_COLUMN, background_color=background_color, size=size, pad=pad, key=key) - return - - def AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - if element.Key is not None: - self.UseDictionary = True - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - def Layout(self, rows): - for row in rows: - self.AddRow(*row) - - def _GetElementAtLocation(self, location): - (row_num,col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - - def __del__(self): - for row in self.Rows: - for element in row: - element.__del__() - try: - del(self.TKFrame) - except: - pass - super().__del__() - - -# ---------------------------------------------------------------------- # -# Calendar # -# ---------------------------------------------------------------------- # -class TKCalendar(tkinter.ttk.Frame): - """ - This code was shamelessly lifted from moshekaplan's repository - moshekaplan/tkinter_components - """ - # XXX ToDo: cget and configure - - datetime = calendar.datetime.datetime - timedelta = calendar.datetime.timedelta - - def __init__(self, master=None, target_element=None, **kw): - """ - WIDGET-SPECIFIC OPTIONS - - locale, firstweekday, year, month, selectbackground, - selectforeground - """ - self._TargetElement = target_element - # remove custom options from kw before initializating ttk.Frame - fwday = kw.pop('firstweekday', calendar.MONDAY) - year = kw.pop('year', self.datetime.now().year) - month = kw.pop('month', self.datetime.now().month) - locale = kw.pop('locale', None) - sel_bg = kw.pop('selectbackground', '#ecffc4') - sel_fg = kw.pop('selectforeground', '#05640e') - - self._date = self.datetime(year, month, 1) - self._selection = None # no date selected - tkinter.ttk.Frame.__init__(self, master, **kw) - - # instantiate proper calendar class - if locale is None: - self._cal = calendar.TextCalendar(fwday) - else: - self._cal = calendar.LocaleTextCalendar(fwday, locale) - - self.__setup_styles() # creates custom styles - self.__place_widgets() # pack/grid used widgets - self.__config_calendar() # adjust calendar columns and setup tags - # configure a canvas, and proper bindings, for selecting dates - self.__setup_selection(sel_bg, sel_fg) - - # store items ids, used for insertion later - self._items = [self._calendar.insert('', 'end', values='') - for _ in range(6)] - # insert dates in the currently empty calendar - self._build_calendar() - - def __setitem__(self, item, value): - if item in ('year', 'month'): - raise AttributeError("attribute '%s' is not writeable" % item) - elif item == 'selectbackground': - self._canvas['background'] = value - elif item == 'selectforeground': - self._canvas.itemconfigure(self._canvas.text, item=value) - else: - tkinter.ttk.Frame.__setitem__(self, item, value) - - def __getitem__(self, item): - if item in ('year', 'month'): - return getattr(self._date, item) - elif item == 'selectbackground': - return self._canvas['background'] - elif item == 'selectforeground': - return self._canvas.itemcget(self._canvas.text, 'fill') - else: - r = tkinter.ttk.tclobjs_to_py({item: tkinter.ttk.Frame.__getitem__(self, item)}) - return r[item] - - def __setup_styles(self): - # custom ttk styles - style = tkinter.ttk.Style(self.master) - arrow_layout = lambda dir: ( - [('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})] - ) - style.layout('L.TButton', arrow_layout('left')) - style.layout('R.TButton', arrow_layout('right')) - - def __place_widgets(self): - # header frame and its widgets - hframe = tkinter.ttk.Frame(self) - lbtn = tkinter.ttk.Button(hframe, style='L.TButton', command=self._prev_month) - rbtn = tkinter.ttk.Button(hframe, style='R.TButton', command=self._next_month) - self._header = tkinter.ttk.Label(hframe, width=15, anchor='center') - # the calendar - self._calendar = tkinter.ttk.Treeview(self, show='', selectmode='none', height=7) - - # pack the widgets - hframe.pack(in_=self, side='top', pady=4, anchor='center') - lbtn.grid(in_=hframe) - self._header.grid(in_=hframe, column=1, row=0, padx=12) - rbtn.grid(in_=hframe, column=2, row=0) - self._calendar.pack(in_=self, expand=1, fill='both', side='bottom') - - def __config_calendar(self): - cols = self._cal.formatweekheader(3).split() - self._calendar['columns'] = cols - self._calendar.tag_configure('header', background='grey90') - self._calendar.insert('', 'end', values=cols, tag='header') - # adjust its columns width - font = tkinter.font.Font() - maxwidth = max(font.measure(col) for col in cols) - for col in cols: - self._calendar.column(col, width=maxwidth, minwidth=maxwidth, - anchor='e') - - def __setup_selection(self, sel_bg, sel_fg): - self._font = tkinter.font.Font() - self._canvas = canvas = tk.Canvas(self._calendar, - background=sel_bg, borderwidth=0, highlightthickness=0) - canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='w') - - canvas.bind('', lambda evt: canvas.place_forget()) - self._calendar.bind('', lambda evt: canvas.place_forget()) - self._calendar.bind('', self._pressed) - - def __minsize(self, evt): - width, height = self._calendar.master.geometry().split('x') - height = height[:height.index('+')] - self._calendar.master.minsize(width, height) - - def _build_calendar(self): - year, month = self._date.year, self._date.month - - # update header text (Month, YEAR) - header = self._cal.formatmonthname(year, month, 0) - self._header['text'] = header.title() - - # update calendar shown dates - cal = self._cal.monthdayscalendar(year, month) - for indx, item in enumerate(self._items): - week = cal[indx] if indx < len(cal) else [] - fmt_week = [('%02d' % day) if day else '' for day in week] - self._calendar.item(item, values=fmt_week) - - def _show_selection(self, text, bbox): - """Configure canvas for a new selection.""" - x, y, width, height = bbox - - textw = self._font.measure(text) - - canvas = self._canvas - canvas.configure(width=width, height=height) - canvas.coords(canvas.text, width - textw, height / 2 - 1) - canvas.itemconfigure(canvas.text, text=text) - canvas.place(in_=self._calendar, x=x, y=y) - - # Callbacks - - def _pressed(self, evt): - """Clicked somewhere in the calendar.""" - x, y, widget = evt.x, evt.y, evt.widget - item = widget.identify_row(y) - column = widget.identify_column(x) - - if not column or not item in self._items: - # clicked in the weekdays row or just outside the columns - return - - item_values = widget.item(item)['values'] - if not len(item_values): # row is empty for this month - return - - text = item_values[int(column[1]) - 1] - if not text: # date is empty - return - - bbox = widget.bbox(item, column) - if not bbox: # calendar not visible yet - return - - # update and then show selection - text = '%02d' % text - self._selection = (text, item, column) - self._show_selection(text, bbox) - year, month = self._date.year, self._date.month - try: - self._TargetElement.Update(self.datetime(year, month, int(self._selection[0]))) - except: pass - - def _prev_month(self): - """Updated calendar to show the previous month.""" - self._canvas.place_forget() - - self._date = self._date - self.timedelta(days=1) - self._date = self.datetime(self._date.year, self._date.month, 1) - self._build_calendar() # reconstuct calendar - - def _next_month(self): - """Update calendar to show the next month.""" - self._canvas.place_forget() - - year, month = self._date.year, self._date.month - self._date = self._date + self.timedelta( - days=calendar.monthrange(year, month)[1] + 1) - self._date = self.datetime(self._date.year, self._date.month, 1) - self._build_calendar() # reconstruct calendar - - # Properties - - @property - def selection(self): - """Return a datetime representing the current selected date.""" - if not self._selection: - return None - - year, month = self._date.year, self._date.month - return self.datetime(year, month, int(self._selection[0])) - - -# ---------------------------------------------------------------------- # -# Menu # -# ---------------------------------------------------------------------- # -class Menu(Element): - def __init__(self, menu_definition, background_color=None, size=(None, None), tearoff=True, pad=None, key=None): - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.MenuDefinition = menu_definition - self.TKMenu = None - self.Tearoff = tearoff - - super().__init__(ELEM_TYPE_MENUBAR, background_color=background_color, size=size, pad=pad, key=key) - return - - def MenuItemChosenCallback(self, item_chosen): - # print('IN MENU ITEM CALLBACK', item_chosen) - self.ParentForm.LastButtonClicked = item_chosen - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def __del__(self): - super().__del__() - - -# ---------------------------------------------------------------------- # -# Table # -# ---------------------------------------------------------------------- # -class Table(Element): - def __init__(self, values, headings=None, visible_column_map=None, col_widths=None, def_col_width=10, auto_size_columns=True, max_col_width=20, select_mode=None, display_row_numbers=False, scrollable=None, font=None, justification='right', text_color=None, background_color=None, size=(None, None), pad=None, key=None, tooltip=None): - self.Values = values - self.ColumnHeadings = headings - self.ColumnsToDisplay = visible_column_map - self.ColumnWidths = col_widths - self.MaxColumnWidth = max_col_width - self.DefaultColumnWidth = def_col_width - self.AutoSizeColumns = auto_size_columns - self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR - self.TextColor = text_color - self.Justification = justification - self.Scrollable = scrollable - self.InitialState = None - self.SelectMode = select_mode - self.DisplayRowNumbers = display_row_numbers - self.TKTreeview = None - - super().__init__(ELEM_TYPE_TABLE, text_color=text_color, background_color=background_color, font=font, size=size, pad=pad, key=key, tooltip=tooltip) - return - - - def __del__(self): - super().__del__() - - -# ---------------------------------------------------------------------- # -# Error Element # -# ---------------------------------------------------------------------- # -class ErrorElement(Element): - def __init__(self, key=None): - - self.Key = key - - super().__init__(ELEM_TYPE_ERROR, key=key) - return - - def Update(self, *args, **kwargs): - PopupError('Keyword error in Update', - 'You need to stop this madness and check your spelling', - 'Bad key = {}'.format(self.Key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(self.Key)) - return self - - - def MenuItemChosenCallback(self, item_chosen): - # print('IN MENU ITEM CALLBACK', item_chosen) - self.ParentForm.LastButtonClicked = item_chosen - self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop - - def __del__(self): - super().__del__() - - - -# ------------------------------------------------------------------------- # -# Window CLASS # -# ------------------------------------------------------------------------- # -class Window(object): - ''' - Display a user defined for and return the filled in data - ''' - def __init__(self, title, default_element_size=DEFAULT_ELEMENT_SIZE, default_button_element_size = (None, None), auto_size_text=None, auto_size_buttons=None, location=(None, None), button_color=None, font=None, progress_bar_color=(None, None), background_color=None, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON, force_toplevel = False, return_keyboard_events=False, use_default_focus=True, text_justification=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False): - self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT - self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS - self.Title = title - self.Rows = [] # a list of ELEMENTS for this row - self.DefaultElementSize = default_element_size - self.DefaultButtonElementSize = default_button_element_size if default_button_element_size != (None, None) else DEFAULT_BUTTON_ELEMENT_SIZE - self.Location = location - self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR - self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR - self.ParentWindow = None - self.Font = font if font else DEFAULT_FONT - self.RadioDict = {} - self.BorderDepth = border_depth - self.WindowIcon = icon if icon is not None else _my_windows.user_defined_icon - self.AutoClose = auto_close - self.NonBlocking = False - self.TKroot = None - self.TKrootDestroyed = False - self.FormRemainedOpen = False - self.TKAfterID = None - self.ProgressBarColor = progress_bar_color - self.AutoCloseDuration = auto_close_duration - self.UberParent = None - self.RootNeedsDestroying = False - self.Shown = False - self.ReturnValues = None - self.ReturnValuesList = [] - self.ReturnValuesDictionary = {} - self.DictionaryKeyCounter = 0 - self.LastButtonClicked = None - self.LastButtonClickedWasRealtime = False - self.UseDictionary = False - self.UseDefaultFocus = use_default_focus - self.ReturnKeyboardEvents = return_keyboard_events - self.LastKeyboardEvent = None - self.TextJustification = text_justification - self.NoTitleBar = no_titlebar - self.GrabAnywhere = grab_anywhere - self.KeepOnTop = keep_on_top - self.ForceTopLevel = force_toplevel - - # ------------------------- Add ONE Row to Form ------------------------- # - def AddRow(self, *args): - ''' Parms are a variable number of Elements ''' - NumRows = len(self.Rows) # number of existing rows is our row number - CurrentRowNumber = NumRows # this row's number - CurrentRow = [] # start with a blank row and build up - # ------------------------- Add the elements to a row ------------------------- # - for i, element in enumerate(args): # Loop through list of elements and add them to the row - element.Position = (CurrentRowNumber, i) - element.ParentContainer = self - CurrentRow.append(element) - # ------------------------- Append the row to list of Rows ------------------------- # - self.Rows.append(CurrentRow) - - # ------------------------- Add Multiple Rows to Form ------------------------- # - def AddRows(self,rows): - for row in rows: - self.AddRow(*row) - - def Layout(self,rows): - self.AddRows(rows) - return self - - def LayoutAndRead(self,rows, non_blocking=False): - self.AddRows(rows) - self.Show(non_blocking=non_blocking) - return self.ReturnValues - - def LayoutAndShow(self, rows): - raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead') - - # ------------------------- ShowForm THIS IS IT! ------------------------- # - def Show(self, non_blocking=False): - self.Shown = True - # Compute num rows & num cols (it'll come in handy debugging) - self.NumRows = len(self.Rows) - self.NumCols = max(len(row) for row in self.Rows) - self.NonBlocking=non_blocking - - # Search through entire form to see if any elements set the focus - # if not, then will set the focus to the first input element - found_focus = False - for row in self.Rows: - for element in row: - try: - if element.Focus: - found_focus = True - except: - pass - try: - if element.Key is not None: - self.UseDictionary = True - except: - pass - - if not found_focus and self.UseDefaultFocus: - self.UseDefaultFocus = True - else: - self.UseDefaultFocus = False - # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## - StartupTK(self) - # If a button or keyboard event happened but no results have been built, build the results - if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - return BuildResults(self, False, self) - return self.ReturnValues - - # ------------------------- SetIcon - set the window's fav icon ------------------------- # - def SetIcon(self, icon): - self.WindowIcon = icon - try: - self.TKroot.iconbitmap(icon) - except: pass - - def _GetElementAtLocation(self, location): - (row_num,col_num) = location - row = self.Rows[row_num] - element = row[col_num] - return element - - def _GetDefaultElementSize(self): - return self.DefaultElementSize - - def _AutoCloseAlarmCallback(self): - try: - if self.UberParent: - window = self.UberParent - else: - window = self - if window: - window._Close() - self.TKroot.quit() - self.RootNeedsDestroying = True - except: - pass - - - def Read(self): - self.NonBlocking = False - if self.TKrootDestroyed: - return None, None - if not self.Shown: - self.Show() - else: - InitializeResults(self) - self.TKroot.mainloop() - if self.RootNeedsDestroying: - self.TKroot.destroy() - _my_windows.Decrement() - # if form was closed with X - if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None: - _my_windows.Decrement() - if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - return BuildResults(self, False, self) - else: - return self.ReturnValues - - - def ReadNonBlocking(self, Message=''): - if self.TKrootDestroyed: - return None, None - if not self.Shown: - self.Show(non_blocking=True) - if Message: - print(Message) - try: - rc = self.TKroot.update() - except: - self.TKrootDestroyed = True - _my_windows.Decrement() - # return None, None - return BuildResults(self, False, self) - - - def Finalize(self): - if self.TKrootDestroyed: - return self - if not self.Shown: - self.Show(non_blocking=True) - try: - rc = self.TKroot.update() - except: - self.TKrootDestroyed = True - _my_windows.Decrement() - # return None, None - return self - - - def Refresh(self): - if self.TKrootDestroyed: - return self - try: - rc = self.TKroot.update() - except: - pass - return self - - - def Fill(self, values_dict): - FillFormWithValues(self, values_dict) - return self - - - def FindElement(self, key): - element = _FindElementFromKeyInSubForm(self, key) - if element is None: - print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') - if element is None: - print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***') - PopupError('Keyword error in FindElement Call', - 'Bad key = {}'.format(key), - 'Your bad line of code may resemble this:', - 'window.FindElement("{}")'.format(key)) - return ErrorElement(key=key) - return element - - def SaveToDisk(self, filename): - try: - results = BuildResults(self, False, self) - with open(filename, 'wb') as sf: - pickle.dump(results[1], sf) - except: - print('*** Error saving form to disk ***') - - - def LoadFromDisk(self, filename): - try: - with open(filename, 'rb') as df: - self.Fill(pickle.load(df)) - except: - print('*** Error loading form to disk ***') - - - - def GetScreenDimensions(self): - if self.TKrootDestroyed: - return None, None - screen_width = self.TKroot.winfo_screenwidth() # get window info to move to middle of screen - screen_height = self.TKroot.winfo_screenheight() - return screen_width, screen_height - - - def StartMove(self, event): - try: - self.TKroot.x = event.x - self.TKroot.y = event.y - except: pass - - def StopMove(self, event): - try: - self.TKroot.x = None - self.TKroot.y = None - except: pass - - def OnMotion(self, event): - try: - deltax = event.x - self.TKroot.x - deltay = event.y - self.TKroot.y - x = self.TKroot.winfo_x() + deltax - y = self.TKroot.winfo_y() + deltay - self.TKroot.geometry("+%s+%s" % (x, y)) - except: - pass - - - def _KeyboardCallback(self, event ): - self.LastButtonClicked = None - self.FormRemainedOpen = True - if event.char != '': - self.LastKeyboardEvent = event.char - else: - self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode) - if not self.NonBlocking: - BuildResults(self, False, self) - self.TKroot.quit() - - def _MouseWheelCallback(self, event ): - self.LastButtonClicked = None - self.FormRemainedOpen = True - self.LastKeyboardEvent = 'MouseWheel:Down' if event.delta < 0 else 'MouseWheel:Up' - if not self.NonBlocking: - BuildResults(self, False, self) - self.TKroot.quit() - - - def _Close(self): - try: - self.TKroot.update() - except: pass - if not self.NonBlocking: - BuildResults(self, False, self) - if self.TKrootDestroyed: - return None - self.TKrootDestroyed = True - self.RootNeedsDestroying = True - return None - - def CloseNonBlocking(self): - if self.TKrootDestroyed: - return - try: - self.TKroot.destroy() - _my_windows.Decrement() - except: pass - - CloseNonBlockingForm = CloseNonBlocking - - def OnClosingCallback(self): - return - - - def Disable(self): - self.TKroot.grab_set_global() - - def Enable(self): - self.TKroot.grab_release() - - def Hide(self): - self.TKroot.withdraw() - - def UnHide(self): - self.TKroot.deiconify() - - - def __enter__(self): - return self - - def __exit__(self, *a): - self.__del__() - return False - - def __del__(self): - for row in self.Rows: - for element in row: - element.__del__() - # try: - # del(self.TKroot) - # except: - # pass - -FlexForm = Window - - -# ------------------------------------------------------------------------- # -# UberForm CLASS # -# Used to make forms into TABS (it's trick) # -# ------------------------------------------------------------------------- # -class UberForm(object): - FormList = None # list of all the forms in this window - FormReturnValues = None - TKroot = None # tk root for the overall window - TKrootDestroyed = False - def __init__(self): - self.FormList = [] - self.FormReturnValues = [] - self.TKroot = None - self.TKrootDestroyed = False - self.FormStayedOpen = False - - def AddForm(self, form): - self.FormList.append(form) - - def _Close(self): - self.FormReturnValues = [] - for form in self.FormList: - form._Close() - self.FormReturnValues.append(form.ReturnValues) - if not self.TKrootDestroyed: - self.TKrootDestroyed = True - self.TKroot.destroy() - _my_windows.Decrement() - - def __del__(self): - return - -# =========================================================================== # -# Button Lazy Functions so the caller doesn't have to define a bunch of stuff # -# =========================================================================== # - - -# ------------------------- FOLDER BROWSE Element lazy function ------------------------- # -def FolderBrowse(button_text='Browse', target=(ThisRow, -1), initial_folder=None, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_BROWSE_FOLDER, target=target, initial_folder=initial_folder, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, pad=pad, key=key) - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileBrowse( button_text='Browse',target=(ThisRow, -1), file_types=(("ALL Files", "*.*"),), initial_folder=None, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_BROWSE_FILE, target=target, file_types=file_types,initial_folder=initial_folder, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, pad=pad, key=key) - -# ------------------------- FILES BROWSE Element (Multiple file selection) lazy function ------------------------- # -def FilesBrowse(button_text='Browse',target=(ThisRow, -1), file_types=(("ALL Files", "*.*"),), initial_folder=None, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_BROWSE_FILES, target=target, file_types=file_types, initial_folder=initial_folder, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, pad=pad, key=key) - -# ------------------------- FILE BROWSE Element lazy function ------------------------- # -def FileSaveAs(button_text='Save As...',target=(ThisRow, -1), file_types=(("ALL Files", "*.*"),), initial_folder=None, tooltip=None,size=(None, None), auto_size_button=None, button_color=None, font=None, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_SAVEAS_FILE, target=target, file_types=file_types, initial_folder=initial_folder, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, pad=pad, key=key) - -# ------------------------- SAVE AS Element lazy function ------------------------- # -def SaveAs(button_text='Save As...',target=(ThisRow, -1), file_types=(("ALL Files", "*.*"),),initial_folder=None,tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_SAVEAS_FILE, target=target, file_types=file_types, initial_folder=initial_folder, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, pad=pad, key=key) - -# ------------------------- SAVE BUTTON Element lazy function ------------------------- # -def Save(button_text='Save', size=(None, None), auto_size_button=None, button_color=None, bind_return_key=True, tooltip=None, font=None, focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color,font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- SUBMIT BUTTON Element lazy function ------------------------- # -def Submit(button_text='Submit', size=(None, None), auto_size_button=None, button_color=None, bind_return_key=True, tooltip=None, font=None, focus=False, pad=None, key=None): - return Button( button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color,font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- OPEN BUTTON Element lazy function ------------------------- # -def Open(button_text='Open', size=(None, None), auto_size_button=None, button_color=None, bind_return_key=True, tooltip=None, font=None, focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color,font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- OK BUTTON Element lazy function ------------------------- # -def OK(button_text='OK', size=(None, None), auto_size_button=None, button_color=None, bind_return_key=True, tooltip=None, font=None,focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color,font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- YES BUTTON Element lazy function ------------------------- # -def Ok(button_text='Ok', size=(None, None), auto_size_button=None, button_color=None, bind_return_key=True, tooltip=None, font=None,focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- CANCEL BUTTON Element lazy function ------------------------- # -def Cancel(button_text='Cancel', size=(None, None), auto_size_button=None, button_color=None, tooltip=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- QUIT BUTTON Element lazy function ------------------------- # -def Quit(button_text='Quit', size=(None, None), auto_size_button=None, button_color=None, tooltip=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- Exit BUTTON Element lazy function ------------------------- # -def Exit(button_text='Exit', size=(None, None), auto_size_button=None, button_color=None, tooltip=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- YES BUTTON Element lazy function ------------------------- # -def Yes(button_text='Yes', size=(None, None), auto_size_button=None, button_color=None, tooltip=None, font=None, bind_return_key=True, focus=False, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- NO BUTTON Element lazy function ------------------------- # -def No(button_text='No', size=(None, None), auto_size_button=None, button_color=None, tooltip=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- NO BUTTON Element lazy function ------------------------- # -def Help(button_text='Help', size=(None, None), auto_size_button=None, button_color=None,font=None,tooltip=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # -def SimpleButton(button_text, image_filename=None, image_size=(None, None), image_subsample=None, border_width=None,tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_CLOSES_WIN, image_filename=image_filename, image_size=image_size, image_subsample=image_subsample, border_width=border_width, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) -# ------------------------- GENERIC BUTTON Element lazy function ------------------------- # -def ReadButton(button_text, image_filename=None, image_size=(None, None),image_subsample=None,border_width=None,tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button( button_text=button_text, button_type=BUTTON_TYPE_READ_FORM, image_filename=image_filename, image_size=image_size, image_subsample=image_subsample, border_width=border_width, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -ReadFormButton = ReadButton -RButton = ReadFormButton - - -# ------------------------- Realtime BUTTON Element lazy function ------------------------- # -def RealtimeButton(button_text, image_filename=None, image_size=(None, None),image_subsample=None,border_width=None,tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button( button_text=button_text,button_type=BUTTON_TYPE_REALTIME, image_filename=image_filename, image_size=image_size, image_subsample=image_subsample, border_width=border_width, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) -# ------------------------- Dummy BUTTON Element lazy function ------------------------- # -def DummyButton(button_text, image_filename=None, image_size=(None, None),image_subsample=None,border_width=None,tooltip=None, size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text, button_type= BUTTON_TYPE_CLOSES_WIN_ONLY, image_filename=image_filename, image_size=image_size, image_subsample=image_subsample, border_width=border_width, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def CalendarButton(button_text, target=(None,None), image_filename=None, image_size=(None, None), image_subsample=None,tooltip=None, border_width=None, size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text, button_type=BUTTON_TYPE_CALENDAR_CHOOSER, target=target, image_filename=image_filename, image_size=image_size, image_subsample=image_subsample, border_width=border_width, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) - -# ------------------------- Calendar Chooser Button lazy function ------------------------- # -def ColorChooserButton(button_text, target=(None,None), image_filename=None, image_size=(None, None), image_subsample=None,tooltip=None, border_width=None, size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, focus=False, pad=None, key=None): - return Button(button_text=button_text,button_type=BUTTON_TYPE_COLOR_CHOOSER, target=target, image_filename=image_filename, image_size=image_size, image_subsample=image_subsample, border_width=border_width, tooltip=tooltip, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font, bind_return_key=bind_return_key, focus=focus, pad=pad, key=key) -##################################### ----- RESULTS ------ ################################################## - -def AddToReturnDictionary(form, element, value): - if element.Key is None: - form.ReturnValuesDictionary[form.DictionaryKeyCounter] = value - element.Key = form.DictionaryKeyCounter - form.DictionaryKeyCounter += 1 - else: - form.ReturnValuesDictionary[element.Key] = value - -def AddToReturnList(form, value): - form.ReturnValuesList.append(value) - - -#----------------------------------------------------------------------------# -# ------- FUNCTION InitializeResults. Sets up form results matrix --------# -def InitializeResults(form): - BuildResults(form, True, form) - return - -#===== Radio Button RadVar encoding and decoding =====# -#===== The value is simply the row * 1000 + col =====# -def DecodeRadioRowCol(RadValue): - row = RadValue//1000 - col = RadValue%1000 - return row,col - -def EncodeRadioRowCol(row, col): - RadValue = row * 1000 + col - return RadValue - -# ------- FUNCTION BuildResults. Form exiting so build the results to pass back ------- # -# format of return values is -# (Button Pressed, input_values) -def BuildResults(form, initialize_only, top_level_form): - # Results for elements are: - # TEXT - Nothing - # INPUT - Read value from TK - # Button - Button Text and position as a Tuple - - # Get the initialized results so we don't have to rebuild - form.DictionaryKeyCounter = 0 - form.ReturnValuesDictionary = {} - form.ReturnValuesList = [] - BuildResultsForSubform(form, initialize_only, top_level_form) - if not top_level_form.LastButtonClickedWasRealtime: - top_level_form.LastButtonClicked = None - return form.ReturnValues - -def BuildResultsForSubform(form, initialize_only, top_level_form): - button_pressed_text = top_level_form.LastButtonClicked - for row_num,row in enumerate(form.Rows): - for col_num, element in enumerate(row): - value = None - if element.Type == ELEM_TYPE_COLUMN: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_FRAME: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB_GROUP: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if element.Type == ELEM_TYPE_TAB: - element.DictionaryKeyCounter = top_level_form.DictionaryKeyCounter - element.ReturnValuesList = [] - element.ReturnValuesDictionary = {} - BuildResultsForSubform(element, initialize_only, top_level_form) - for item in element.ReturnValuesList: - AddToReturnList(top_level_form, item) - if element.UseDictionary: - top_level_form.UseDictionary = True - if element.ReturnValues[0] is not None: # if a button was clicked - button_pressed_text = element.ReturnValues[0] - - if not initialize_only: - if element.Type == ELEM_TYPE_INPUT_TEXT: - value=element.TKStringVar.get() - if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: - element.TKStringVar.set('') - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - value = element.TKIntVar.get() - value = (value != 0) - elif element.Type == ELEM_TYPE_INPUT_RADIO: - RadVar=element.TKIntVar.get() - this_rowcol = EncodeRadioRowCol(row_num,col_num) - value = RadVar == this_rowcol - elif element.Type == ELEM_TYPE_BUTTON: - if top_level_form.LastButtonClicked == element.ButtonText: - button_pressed_text = top_level_form.LastButtonClicked - if element.BType != BUTTON_TYPE_REALTIME: # Do not clear realtime buttons - top_level_form.LastButtonClicked = None - if element.BType == BUTTON_TYPE_CALENDAR_CHOOSER: - try: - value = element.TKCal.selection - except: - value = None - else: - try: - value = element.TKStringVar.get() - except: - value = None - elif element.Type == ELEM_TYPE_INPUT_COMBO: - value=element.TKStringVar.get() - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - value=element.TKStringVar.get() - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - try: - items=element.TKListbox.curselection() - value = [element.Values[int(item)] for item in items] - except: - value = '' - elif element.Type == ELEM_TYPE_INPUT_SPIN: - try: - value=element.TKStringVar.get() - except: - value = 0 - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - try: - value=element.TKIntVar.get() - except: - value = 0 - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - try: - value=element.TKText.get(1.0, tk.END) - if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: - element.TKText.delete('1.0', tk.END) - except: - value = None - elif element.Type == ELEM_TYPE_TAB_GROUP: - try: - value=element.TKNotebook.tab(element.TKNotebook.index('current'))['text'] - tab_key = element.FindKeyFromTabName(value) - if tab_key is not None: - value = tab_key - except: - value = None - else: - value = None - - # if an input type element, update the results - if element.Type != ELEM_TYPE_BUTTON and element.Type != ELEM_TYPE_TEXT and element.Type != ELEM_TYPE_IMAGE and\ - element.Type != ELEM_TYPE_OUTPUT and element.Type != ELEM_TYPE_PROGRESS_BAR and \ - element.Type!= ELEM_TYPE_COLUMN and element.Type != ELEM_TYPE_FRAME \ - and element.Type != ELEM_TYPE_TAB: - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - elif (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_CALENDAR_CHOOSER and element.Target == (None,None)) or \ - (element.Type == ELEM_TYPE_BUTTON and element.BType == BUTTON_TYPE_COLOR_CHOOSER and element.Target == (None,None)) or \ - (element.Type == ELEM_TYPE_BUTTON and element.Key is not None and (element.BType in (BUTTON_TYPE_SAVEAS_FILE, BUTTON_TYPE_BROWSE_FILE, BUTTON_TYPE_BROWSE_FILES, BUTTON_TYPE_BROWSE_FOLDER))): - AddToReturnList(form, value) - AddToReturnDictionary(top_level_form, element, value) - - # if this is a column, then will fail so need to wrap with tr - try: - if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: - button_pressed_text = form.LastKeyboardEvent - form.LastKeyboardEvent = None - except: pass - - try: - form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included - except: pass - - if not form.UseDictionary: - form.ReturnValues = button_pressed_text, form.ReturnValuesList - else: - form.ReturnValues = button_pressed_text, form.ReturnValuesDictionary - - return form.ReturnValues - -def FillFormWithValues(form, values_dict): - FillSubformWithValues(form, values_dict) - -def FillSubformWithValues(form, values_dict): - for row_num,row in enumerate(form.Rows): - for col_num, element in enumerate(row): - value = None - if element.Type == ELEM_TYPE_COLUMN: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_FRAME: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB_GROUP: - FillSubformWithValues(element, values_dict) - if element.Type == ELEM_TYPE_TAB: - FillSubformWithValues(element, values_dict) - try: - value = values_dict[element.Key] - except: - continue - if element.Type == ELEM_TYPE_INPUT_TEXT: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_RADIO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_COMBO: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_OPTION_MENU: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_LISTBOX: - element.SetValue(value) - elif element.Type == ELEM_TYPE_INPUT_SLIDER: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_MULTILINE: - element.Update(value) - elif element.Type == ELEM_TYPE_INPUT_SPIN: - element.Update(value) - elif element.Type == ELEM_TYPE_BUTTON: - element.Update(value) - -def _FindElementFromKeyInSubForm(form, key): - for row_num, row in enumerate(form.Rows): - for col_num, element in enumerate(row): - if element.Type == ELEM_TYPE_COLUMN: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_FRAME: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB_GROUP: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Type == ELEM_TYPE_TAB: - matching_elem = _FindElementFromKeyInSubForm(element, key) - if matching_elem is not None: - return matching_elem - if element.Key == key: - return element - - -def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False): - if type(sub_menu_info) is str: - if not is_sub_menu and not skip: - # print(f'Adding command {sub_menu_info}') - pos = sub_menu_info.find('&') - if pos != -1: - if pos == 0 or sub_menu_info[pos-1] != "\\": - sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos+1:] - if sub_menu_info == '---': - top_menu.add('separator') - else: - top_menu.add_command(label=sub_menu_info, underline=pos, command=lambda: Menu.MenuItemChosenCallback(element, sub_menu_info)) - else: - i = 0 - while i < (len(sub_menu_info)): - item = sub_menu_info[i] - if i != len(sub_menu_info) - 1: - if type(sub_menu_info[i+1]) == list: - new_menu = tk.Menu(top_menu, tearoff=element.Tearoff) - top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu) - AddMenuItem(new_menu, sub_menu_info[i+1], element, is_sub_menu=True) - i += 1 # skip the next one - else: - AddMenuItem(top_menu, item, element) - else: - AddMenuItem(top_menu, item, element) - i += 1 - -# ------------------------------------------------------------------------------------------------------------------ # -# ===================================== TK CODE STARTS HERE ====================================================== # -# ------------------------------------------------------------------------------------------------------------------ # - -def PackFormIntoFrame(form, containing_frame, toplevel_form): - def CharWidthInPixels(): - return tkinter.font.Font().measure('A') # single character width - # only set title on non-tabbed forms - border_depth = toplevel_form.BorderDepth if toplevel_form.BorderDepth is not None else DEFAULT_BORDER_WIDTH - # --------------------------------------------------------------------------- # - # **************** Use FlexForm to build the tkinter window ********** ----- # - # Building is done row by row. # - # --------------------------------------------------------------------------- # - focus_set = False - ######################### LOOP THROUGH ROWS ######################### - # *********** ------- Loop through ROWS ------- ***********# - for row_num, flex_row in enumerate(form.Rows): - ######################### LOOP THROUGH ELEMENTS ON ROW ######################### - # *********** ------- Loop through ELEMENTS ------- ***********# - # *********** Make TK Row ***********# - tk_row_frame = tk.Frame(containing_frame) - for col_num, element in enumerate(flex_row): - element.ParentForm = toplevel_form # save the button's parent form object - if toplevel_form.Font and (element.Font == DEFAULT_FONT or not element.Font): - font = toplevel_form.Font - elif element.Font is not None: - font = element.Font - else: - font = DEFAULT_FONT - # ------- Determine Auto-Size setting on a cascading basis ------- # - if element.AutoSizeText is not None: # if element overide - auto_size_text = element.AutoSizeText - elif toplevel_form.AutoSizeText is not None: # if form override - auto_size_text = toplevel_form.AutoSizeText - else: - auto_size_text = DEFAULT_AUTOSIZE_TEXT - element_type = element.Type - # Set foreground color - text_color = element.TextColor - # Determine Element size - element_size = element.Size - if (element_size == (None, None) and element_type != ELEM_TYPE_BUTTON): # user did not specify a size - element_size = toplevel_form.DefaultElementSize - elif (element_size == (None, None) and element_type == ELEM_TYPE_BUTTON): - element_size = toplevel_form.DefaultButtonElementSize - else: auto_size_text = False # if user has specified a size then it shouldn't autosize - # ------------------------- COLUMN element ------------------------- # - if element_type == ELEM_TYPE_COLUMN: - if element.Scrollable: - col_frame = TkScrollableFrame(tk_row_frame) # do not use yet! not working - PackFormIntoFrame(element, col_frame.TKFrame, toplevel_form) - col_frame.TKFrame.update() - if element.Size == (None, None): # if no size specified, use column width x column height/2 - col_frame.canvas.config(width=col_frame.TKFrame.winfo_reqwidth(),height=col_frame.TKFrame.winfo_reqheight()/2) - else: - col_frame.canvas.config(width=element.Size[0],height=element.Size[1]) - - if not element.BackgroundColor in (None, COLOR_SYSTEM_DEFAULT): - col_frame.canvas.config(background=element.BackgroundColor) - col_frame.TKFrame.config(background=element.BackgroundColor, borderwidth =0, highlightthickness=0) - col_frame.config(background=element.BackgroundColor, borderwidth =0, highlightthickness=0) - else: - col_frame = tk.Frame(tk_row_frame) - PackFormIntoFrame(element, col_frame, toplevel_form) - - col_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - col_frame.configure(background=element.BackgroundColor, highlightbackground=element.BackgroundColor, highlightcolor=element.BackgroundColor) - - # ------------------------- TEXT element ------------------------- # - elif element_type == ELEM_TYPE_TEXT: - # auto_size_text = element.AutoSizeText - display_text = element.DisplayText # text to display - if auto_size_text is False: - width, height=element_size - else: - lines = display_text.split('\n') - max_line_len = max([len(l) for l in lines]) - num_lines = len(lines) - if max_line_len > element_size[0]: # if text exceeds element size, the will have to wrap - width = element_size[0] - else: - width=max_line_len - height=num_lines - # ---===--- LABEL widget create and place --- # - stringvar = tk.StringVar() - element.TKStringVar = stringvar - stringvar.set(display_text) - if auto_size_text: - width = 0 - if element.Justification is not None: - justification = element.Justification - elif toplevel_form.TextJustification is not None: - justification = toplevel_form.TextJustification - else: - justification = DEFAULT_TEXT_JUSTIFICATION - justify = tk.LEFT if justification == 'left' else tk.CENTER if justification == 'center' else tk.RIGHT - anchor = tk.NW if justification == 'left' else tk.N if justification == 'center' else tk.NE - tktext_label = tk.Label(tk_row_frame, textvariable=stringvar, width=width, height=height, justify=justify, bd=border_depth, font=font) - # Set wrap-length for text (in PIXELS) == PAIN IN THE ASS - wraplen = tktext_label.winfo_reqwidth()+40 # width of widget in Pixels - if not auto_size_text and height == 1: - wraplen = 0 - # print("wraplen, width, height", wraplen, width, height) - tktext_label.configure(anchor=anchor, wraplen=wraplen) # set wrap to width of widget - if element.Relief is not None: - tktext_label.configure(relief=element.Relief) - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - tktext_label.configure(background=element.BackgroundColor) - if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - tktext_label.configure(fg=element.TextColor) - tktext_label.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1], expand=True) - element.TKText = tktext_label - if element.ClickSubmits: - tktext_label.bind('', element.TextClickedHandler) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKText, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- BUTTON element ------------------------- # - elif element_type == ELEM_TYPE_BUTTON: - stringvar = tk.StringVar() - element.TKStringVar = stringvar - element.Location = (row_num, col_num) - btext = element.ButtonText - btype = element.BType - if element.AutoSizeButton is not None: - auto_size = element.AutoSizeButton - else: auto_size = toplevel_form.AutoSizeButtons - if auto_size is False or element.Size[0] is not None: - width, height = element_size - else: - width = 0 - height= toplevel_form.DefaultButtonElementSize[1] - if element.ButtonColor != (None, None)and element.ButtonColor != DEFAULT_BUTTON_COLOR: - bc = element.ButtonColor - elif toplevel_form.ButtonColor != (None, None) and toplevel_form.ButtonColor != DEFAULT_BUTTON_COLOR: - bc = toplevel_form.ButtonColor - else: - bc = DEFAULT_BUTTON_COLOR - border_depth = element.BorderWidth - if btype != BUTTON_TYPE_REALTIME: - tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height,command=element.ButtonCallBack, justify=tk.LEFT, bd=border_depth, font=font) - else: - tkbutton = tk.Button(tk_row_frame, text=btext, width=width, height=height, justify=tk.LEFT, bd=border_depth, font=font) - tkbutton.bind('', element.ButtonReleaseCallBack) - tkbutton.bind('', element.ButtonPressCallBack) - if bc != (None, None) and bc != COLOR_SYSTEM_DEFAULT: - tkbutton.config(foreground=bc[0], background=bc[1], activebackground=bc[1]) - element.TKButton = tkbutton # not used yet but save the TK button in case - wraplen = tkbutton.winfo_reqwidth() # width of widget in Pixels - if element.ImageFilename: # if button has an image on it - tkbutton.config(highlightthickness=0) - photo = tk.PhotoImage(file=element.ImageFilename) - if element.ImageSize != (None, None): - width, height = element.ImageSize - if element.ImageSubsample: - photo = photo.subsample(element.ImageSubsample) - else: - width, height = photo.width(), photo.height() - tkbutton.config(image=photo, width=width, height=height) - tkbutton.image = photo - if width != 0: - tkbutton.configure(wraplength=wraplen+10) # set wrap to width of widget - tkbutton.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - if element.BindReturnKey: - element.TKButton.bind('', element.ReturnKeyHandler) - if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - focus_set = True - element.TKButton.bind('', element.ReturnKeyHandler) - element.TKButton.focus_set() - toplevel_form.TKroot.focus_force() - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKButton, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT (Single Line) element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_TEXT: - default_text = element.DefaultText - element.TKStringVar = tk.StringVar() - element.TKStringVar.set(default_text) - show = element.PasswordCharacter if element.PasswordCharacter else "" - if element.Justification is not None: - justification = element.Justification - else: - justification = DEFAULT_TEXT_JUSTIFICATION - justify = tk.LEFT if justification == 'left' else tk.CENTER if justification == 'center' else tk.RIGHT - # anchor = tk.NW if justification == 'left' else tk.N if justification == 'center' else tk.NE - element.TKEntry = tk.Entry(tk_row_frame, width=element_size[0], textvariable=element.TKStringVar, bd=border_depth, font=font, show=show, justify=justify) - element.TKEntry.bind('', element.ReturnKeyHandler) - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element.TKEntry.configure(background=element.BackgroundColor) - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKEntry.configure(fg=text_color) - element.TKEntry.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='x') - if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - focus_set = True - element.TKEntry.focus_set() - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKEntry, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- COMBO BOX (Drop Down) element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_COMBO: - max_line_len = max([len(str(l)) for l in element.Values]) - if auto_size_text is False: width=element_size[0] - else: width = max_line_len - element.TKStringVar = tk.StringVar() - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - combostyle = tkinter.ttk.Style() - try: - combostyle.theme_create('combostyle', - settings={'TCombobox': - {'configure': - {'selectbackground': element.BackgroundColor, - 'fieldbackground': element.BackgroundColor, - 'foreground': text_color, - 'background': element.BackgroundColor} - }}) - except: - try: - combostyle.theme_settings('combostyle', - settings={'TCombobox': - {'configure': - {'selectbackground': element.BackgroundColor, - 'fieldbackground': element.BackgroundColor, - 'foreground': text_color, - 'background': element.BackgroundColor} - }}) - except: pass - # ATTENTION: this applies the new style 'combostyle' to all ttk.Combobox - combostyle.theme_use('combostyle') - element.TKCombo = tkinter.ttk.Combobox(tk_row_frame, width=width, textvariable=element.TKStringVar,font=font ) - if element.Size[1] != 1 and element.Size[1] is not None: - element.TKCombo.configure(height=element.Size[1]) - # element.TKCombo['state']='readonly' - element.TKCombo['values'] = element.Values - if element.InitializeAsDisabled: - element.TKCombo['state'] = 'disabled' - # if element.BackgroundColor is not None: - # element.TKCombo.configure(background=element.BackgroundColor) - element.TKCombo.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1]) - if element.DefaultValue: - for i, v in enumerate(element.Values): - if v == element.DefaultValue: - element.TKCombo.current(i) - break - else: - element.TKCombo.current(0) - if element.ChangeSubmits: - element.TKCombo.bind('<>', element.ComboboxSelectHandler) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKCombo, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- OPTION MENU (Like ComboBox but different) element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_OPTION_MENU: - max_line_len = max([len(str(l)) for l in element.Values]) - if auto_size_text is False: width=element_size[0] - else: width = max_line_len - element.TKStringVar = tk.StringVar() - default = element.DefaultValue if element.DefaultValue else element.Values[0] - element.TKStringVar.set(default) - element.TKOptionMenu = tk.OptionMenu(tk_row_frame, element.TKStringVar ,*element.Values) - element.TKOptionMenu.config(highlightthickness=0, font=font, width=width ) - element.TKOptionMenu.config(borderwidth=border_depth) - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element.TKOptionMenu.configure(background=element.BackgroundColor) - if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - element.TKOptionMenu.configure(fg=element.TextColor) - element.TKOptionMenu.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1]) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKOptionMenu, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- LISTBOX element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_LISTBOX: - max_line_len = max([len(str(l)) for l in element.Values]) - if auto_size_text is False: width=element_size[0] - else: width = max_line_len - listbox_frame = tk.Frame(tk_row_frame) - element.TKStringVar = tk.StringVar() - element.TKListbox= tk.Listbox(listbox_frame, height=element_size[1], width=width, selectmode=element.SelectMode, font=font) - for index, item in enumerate(element.Values): - element.TKListbox.insert(tk.END, item) - if element.DefaultValues is not None and item in element.DefaultValues: - element.TKListbox.selection_set(index) - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element.TKListbox.configure(background=element.BackgroundColor) - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKListbox.configure(fg=text_color) - if element.ChangeSubmits: - element.TKListbox.bind('<>', element.ListboxSelectHandler) - vsb = tk.Scrollbar(listbox_frame, orient="vertical", command=element.TKListbox.yview) - element.TKListbox.configure(yscrollcommand=vsb.set) - element.TKListbox.pack(side=tk.LEFT) - vsb.pack(side=tk.LEFT, fill='y') - listbox_frame.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1]) - if element.BindReturnKey: - element.TKListbox.bind('', element.ReturnKeyHandler) - element.TKListbox.bind('', element.ReturnKeyHandler) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKListbox, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT MULTI LINE element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_MULTILINE: - default_text = element.DefaultText - width, height = element_size - element.TKText = tk.scrolledtext.ScrolledText(tk_row_frame, width=width, height=height, wrap='word', bd=border_depth,font=font) - element.TKText.insert(1.0, default_text) # set the default text - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element.TKText.configure(background=element.BackgroundColor) - element.TKText.vbar.config(troughcolor=DEFAULT_SCROLLBAR_COLOR) - element.TKText.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') - if element.EnterSubmits: - element.TKText.bind('', element.ReturnKeyHandler) - if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): - focus_set = True - element.TKText.focus_set() - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKText.configure(fg=text_color) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKText, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT CHECKBOX element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_CHECKBOX: - width = 0 if auto_size_text else element_size[0] - default_value = element.InitialState - element.TKIntVar = tk.IntVar() - element.TKIntVar.set(default_value if default_value is not None else 0) - if element.ChangeSubmits: - element.TKCheckbutton = tk.Checkbutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, - variable=element.TKIntVar, bd=border_depth, font=font, - command=element.CheckboxHandler) - else: - element.TKCheckbutton = tk.Checkbutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, - variable=element.TKIntVar, bd=border_depth, font=font) - if default_value is None: - element.TKCheckbutton.configure(state='disable') - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element.TKCheckbutton.configure(background=element.BackgroundColor) - element.TKCheckbutton.configure(selectcolor=element.BackgroundColor) - element.TKCheckbutton.configure(activebackground=element.BackgroundColor) - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKCheckbutton.configure(fg=text_color) - element.TKCheckbutton.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1]) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKCheckbutton, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- PROGRESS BAR element ------------------------- # - elif element_type == ELEM_TYPE_PROGRESS_BAR: - # save this form because it must be 'updated' (refreshed) solely for the purpose of updating bar - width = element_size[0] - fnt = tkinter.font.Font() - char_width = fnt.measure('A') # single character width - progress_length = width*char_width - progress_width = element_size[1] - direction = element.Orientation - if element.BarColor != (None, None): # if element has a bar color, use it - bar_color = element.BarColor - else: - bar_color = DEFAULT_PROGRESS_BAR_COLOR - element.TKProgressBar = TKProgressBar(tk_row_frame, element.MaxValue, progress_length, progress_width, orientation=direction, BarColor=bar_color, border_width=element.BorderWidth, relief=element.Relief, style=element.BarStyle ) - element.TKProgressBar.TKProgressBarForReal.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - # ------------------------- INPUT RADIO BUTTON element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_RADIO: - width = 0 if auto_size_text else element_size[0] - default_value = element.InitialState - ID = element.GroupID - # see if ID has already been placed - value = EncodeRadioRowCol(row_num, col_num) # value to set intvar to if this radio is selected - if ID in toplevel_form.RadioDict: - RadVar = toplevel_form.RadioDict[ID] - else: - RadVar = tk.IntVar() - toplevel_form.RadioDict[ID] = RadVar - element.TKIntVar = RadVar # store the RadVar in Radio object - if default_value: # if this radio is the one selected, set RadVar to match - element.TKIntVar.set(value) - element.TKRadio = tk.Radiobutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, - variable=element.TKIntVar, value=value, bd=border_depth, font=font) - if not element.BackgroundColor in (None, COLOR_SYSTEM_DEFAULT): - element.TKRadio.configure(background=element.BackgroundColor) - element.TKRadio.configure(selectcolor=element.BackgroundColor) - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKRadio.configure(fg=text_color) - element.TKRadio.pack(side=tk.LEFT, padx=element.Pad[0],pady=element.Pad[1]) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKRadio, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT SPIN Box element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SPIN: - width, height = element_size - width = 0 if auto_size_text else element_size[0] - element.TKStringVar = tk.StringVar() - element.TKSpinBox = tk.Spinbox(tk_row_frame, values=element.Values, textvariable=element.TKStringVar, width=width, bd=border_depth) - element.TKStringVar.set(element.DefaultValue) - element.TKSpinBox.configure(font=font) # set wrap to width of widget - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element.TKSpinBox.configure(background=element.BackgroundColor) - element.TKSpinBox.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKSpinBox.configure(fg=text_color) - if element.ChangeSubmits: - element.TKSpinBox.bind('', element.SpinChangedHandler) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKSpinBox, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- OUTPUT element ------------------------- # - elif element_type == ELEM_TYPE_OUTPUT: - width, height = element_size - element._TKOut = TKOutput(tk_row_frame, width=width, height=height, bd=border_depth, background_color=element.BackgroundColor, text_color=text_color, font=font, pad=element.Pad) - element._TKOut.pack(side=tk.LEFT, expand=True, fill='both') - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element._TKOut, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- IMAGE Box element ------------------------- # - elif element_type == ELEM_TYPE_IMAGE: - if element.Filename is not None: - photo = tk.PhotoImage(file=element.Filename) - elif element.Data is not None: - photo = tk.PhotoImage(data=element.Data) - else: - photo = None - print('*ERROR laying out form.... Image Element has no image specified*') - - if photo is not None: - if element_size == (None, None) or element_size == None or element_size == toplevel_form.DefaultElementSize: - width, height = photo.width(), photo.height() - else: - width, height = element_size - if photo is not None: - element.tktext_label = tk.Label(tk_row_frame, image=photo, width=width, height=height, bd=border_depth) - else: - element.tktext_label = tk.Label(tk_row_frame, width=width, height=height, bd=border_depth) - element.tktext_label.image = photo - # tktext_label.configure(anchor=tk.NW, image=photo) - element.tktext_label.pack(side=tk.LEFT, padx=element.Pad[0],pady=element.Pad[1]) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.tktext_label, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Canvas element ------------------------- # - elif element_type == ELEM_TYPE_CANVAS: - width, height = element_size - if element._TKCanvas is None: - element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) - else: - element._TKCanvas.master = tk_row_frame - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) - element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Graph element ------------------------- # - elif element_type == ELEM_TYPE_GRAPH: - width, height = element_size - if element._TKCanvas is None: - element._TKCanvas = tk.Canvas(tk_row_frame, width=width, height=height, bd=border_depth) - else: - element._TKCanvas.master = tk_row_frame - element._TKCanvas2 = tk.Canvas(element._TKCanvas, width=width, height=height, bd=border_depth) - element._TKCanvas2.pack(side=tk.LEFT) - element._TKCanvas2.addtag_all('mytag') - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - element._TKCanvas2.configure(background=element.BackgroundColor, highlightthickness=0) - element._TKCanvas.configure(background=element.BackgroundColor, highlightthickness=0) - element._TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element._TKCanvas, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- MENUBAR element ------------------------- # - elif element_type == ELEM_TYPE_MENUBAR: - menu_def = element.MenuDefinition - element.TKMenu = tk.Menu(toplevel_form.TKroot, tearoff=element.Tearoff) # create the menubar - menubar = element.TKMenu - for menu_entry in menu_def: - # print(f'Adding a Menubar ENTRY {menu_entry}') - baritem = tk.Menu(menubar, tearoff=element.Tearoff) - pos = menu_entry[0].find('&') - # print(pos) - if pos != -1: - if pos == 0 or menu_entry[0][pos-1] != "\\": - menu_entry[0] = menu_entry[0][:pos] + menu_entry[0][pos+1:] - menubar.add_cascade(label=menu_entry[0], menu=baritem, underline = pos) - if len(menu_entry) > 1: - AddMenuItem(baritem, menu_entry[1], element) - toplevel_form.TKroot.configure(menu=element.TKMenu) - # ------------------------- Frame element ------------------------- # - elif element_type == ELEM_TYPE_FRAME: - labeled_frame = tk.LabelFrame(tk_row_frame, text=element.Title, relief=element.Relief) - PackFormIntoFrame(element, labeled_frame, toplevel_form) - labeled_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - labeled_frame.configure(background=element.BackgroundColor, highlightbackground=element.BackgroundColor, highlightcolor=element.BackgroundColor) - if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - labeled_frame.configure(foreground=element.TextColor) - if font is not None: - labeled_frame.configure(font=font) - if element.TitleLocation is not None: - labeled_frame.configure(labelanchor=element.TitleLocation) - if element.BorderWidth is not None: - labeled_frame.configure(borderwidth=element.BorderWidth) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(labeled_frame, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- Tab element ------------------------- # - elif element_type == ELEM_TYPE_TAB: - element.TKFrame = tk.Frame(form.TKNotebook) - PackFormIntoFrame(element, element.TKFrame, toplevel_form) - form.TKNotebook.add(element.TKFrame, text=element.Title) - form.TKNotebook.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1]) - - if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - element.TKFrame.configure(background=element.BackgroundColor, - highlightbackground=element.BackgroundColor, - highlightcolor=element.BackgroundColor) - # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - # element.TKFrame.configure(foreground=element.TextColor) - if element.BorderWidth is not None: - element.TKFrame.configure(borderwidth=element.BorderWidth) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKFrame, text=element.Tooltip, - timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- TabGroup element ------------------------- # - elif element_type == ELEM_TYPE_TAB_GROUP: - element.TKNotebook = tkinter.ttk.Notebook(tk_row_frame) - PackFormIntoFrame(element, toplevel_form.TKroot, toplevel_form) - - # element.TKNotebook.pack(side=tk.LEFT) - # if element.BackgroundColor != COLOR_SYSTEM_DEFAULT and element.BackgroundColor is not None: - # element.TKNotebook.configure(background=element.BackgroundColor, - # highlightbackground=element.BackgroundColor, - # highlightcolor=element.BackgroundColor) - # if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: - # element.TKNotebook.configure(foreground=element.TextColor) - if element.ChangeSubmits: - element.TKNotebook.bind('<>', element.TabGroupSelectHandler) - if element.BorderWidth is not None: - element.TKNotebook.configure(borderwidth=element.BorderWidth) - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKNotebook, text=element.Tooltip, - timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- SLIDER Box element ------------------------- # - elif element_type == ELEM_TYPE_INPUT_SLIDER: - slider_length = element_size[0] * CharWidthInPixels() - slider_width = element_size[1] - element.TKIntVar = tk.IntVar() - element.TKIntVar.set(element.DefaultValue) - if element.Orientation[0] == 'v': - range_from = element.Range[1] - range_to = element.Range[0] - slider_length += DEFAULT_MARGINS[1]*(element_size[0]*2) # add in the padding - else: - range_from = element.Range[0] - range_to = element.Range[1] - if element.ChangeSubmits: - tkscale = tk.Scale(tk_row_frame, orient=element.Orientation, variable=element.TKIntVar, from_=range_from, to_=range_to, resolution = element.Resolution, length=slider_length, width=slider_width , bd=element.BorderWidth, relief=element.Relief, font=font, command=element.SliderChangedHandler) - else: - tkscale = tk.Scale(tk_row_frame, orient=element.Orientation, variable=element.TKIntVar, from_=range_from, to_=range_to, resolution = element.Resolution, length=slider_length, width=slider_width , bd=element.BorderWidth, relief=element.Relief, font=font) - tkscale.config(highlightthickness=0) - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - tkscale.configure(background=element.BackgroundColor) - if DEFAULT_SCROLLBAR_COLOR != COLOR_SYSTEM_DEFAULT: - tkscale.config(troughcolor=DEFAULT_SCROLLBAR_COLOR) - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - tkscale.configure(fg=text_color) - tkscale.pack(side=tk.LEFT, padx=element.Pad[0],pady=element.Pad[1]) - element.TKScale = tkscale - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKScale, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- TABLE element ------------------------- # - elif element_type == ELEM_TYPE_TABLE: - width, height = element_size - if element.Justification == 'left': - anchor = tk.W - elif element.Justification == 'right': - anchor = tk.E - else: - anchor = tk.CENTER - column_widths = {} - for row in element.Values: - for i,col in enumerate(row): - col_width = min(len(str(col)), element.MaxColumnWidth) - try: - if col_width > column_widths[i]: - column_widths[i] = col_width - except: - column_widths[i] = col_width - if element.ColumnsToDisplay is None: - displaycolumns = element.ColumnHeadings - else: - displaycolumns = [] - for i, should_display in enumerate(element.ColumnsToDisplay): - if should_display: - displaycolumns.append(element.ColumnHeadings[i]) - column_headings= element.ColumnHeadings - if element.DisplayRowNumbers: # if display row number, tack on the numbers to front of columns - displaycolumns = ['Row',] + displaycolumns - column_headings = ['Row',] + element.ColumnHeadings - element.TKTreeview = tkinter.ttk.Treeview(tk_row_frame, columns=column_headings, - displaycolumns=displaycolumns, show='headings', height=height, selectmode=element.SelectMode) - treeview = element.TKTreeview - if element.DisplayRowNumbers: - treeview.heading('Row', text='Row') # make a dummy heading - treeview.column('Row', width=50, anchor=anchor) - for i, heading in enumerate(element.ColumnHeadings): - treeview.heading(heading, text=heading) - if element.AutoSizeColumns: - width = max(column_widths[i], len(heading)) - else: - try: - width = element.ColumnWidths[i] - except: - width = element.DefaultColumnWidth - - treeview.column(heading, width=width*CharWidthInPixels(), anchor=anchor) - for i, value in enumerate(element.Values): - if element.DisplayRowNumbers: - value = [i] + value - id = treeview.insert('', 'end', text=value, values=value) - if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: - tkinter.ttk.Style().configure("Treeview", background=element.BackgroundColor, fieldbackground=element.BackgroundColor) - if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - tkinter.ttk.Style().configure("Treeview", foreground=element.TextColor) - # scrollable_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both') - element.TKTreeview.pack(side=tk.LEFT,expand=True, padx=0, pady=0, fill='both') - if element.Tooltip is not None: - element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - #............................DONE WITH ROW pack the row of widgets ..........................# - # done with row, pack the row of widgets - # tk_row_frame.grid(row=row_num+2, sticky=tk.NW, padx=DEFAULT_MARGINS[0]) - tk_row_frame.pack(side=tk.TOP, anchor='nw', padx=DEFAULT_MARGINS[0], expand=True) - if form.BackgroundColor is not None and form.BackgroundColor != COLOR_SYSTEM_DEFAULT: - tk_row_frame.configure(background=form.BackgroundColor) - toplevel_form.TKroot.configure(padx=DEFAULT_MARGINS[0], pady=DEFAULT_MARGINS[1]) - return - - -def ConvertFlexToTK(MyFlexForm): - master = MyFlexForm.TKroot - # only set title on non-tabbed forms - master.title(MyFlexForm.Title) - InitializeResults(MyFlexForm) - try: - if MyFlexForm.NoTitleBar: - MyFlexForm.TKroot.wm_overrideredirect(True) - except: - pass - PackFormIntoFrame(MyFlexForm, master, MyFlexForm) - #....................................... DONE creating and laying out window ..........................# - screen_width = master.winfo_screenwidth() # get window info to move to middle of screen - screen_height = master.winfo_screenheight() - if MyFlexForm.Location != (None, None): - x,y = MyFlexForm.Location - elif DEFAULT_WINDOW_LOCATION != (None, None): - x,y = DEFAULT_WINDOW_LOCATION - else: - master.update_idletasks() # don't forget to do updates or values are bad - win_width = master.winfo_width() - win_height = master.winfo_height() - x = screen_width/2 -win_width/2 - y = screen_height/2 - win_height/2 - if y+win_height > screen_height: - y = screen_height-win_height - if x+win_width > screen_width: - x = screen_width-win_width - - move_string = '+%i+%i'%(int(x),int(y)) - master.geometry(move_string) - - master.update_idletasks() # don't forget - - return - - - -# ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# -def StartupTK(my_flex_form): - global _my_windows - - ow = _my_windows.NumOpenWindows - - # print('Starting TK open Windows = {}'.format(ow)) - if not ow and not my_flex_form.ForceTopLevel: - root = tk.Tk() - else: - root = tk.Toplevel() - - try: - root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' - except: - pass - # root.wm_overrideredirect(True) - if my_flex_form.BackgroundColor is not None and my_flex_form.BackgroundColor != COLOR_SYSTEM_DEFAULT: - root.configure(background=my_flex_form.BackgroundColor) - _my_windows.Increment() - - my_flex_form.TKroot = root - # Make moveable window - if (my_flex_form.GrabAnywhere is not False and not (my_flex_form.NonBlocking and my_flex_form.GrabAnywhere is not True)): - root.bind("", my_flex_form.StartMove) - root.bind("", my_flex_form.StopMove) - root.bind("", my_flex_form.OnMotion) - - if my_flex_form.KeepOnTop: - root.wm_attributes("-topmost", 1) - - # root.protocol("WM_DELETE_WINDOW", MyFlexForm.DestroyedCallback()) - # root.bind('', MyFlexForm.DestroyedCallback()) - ConvertFlexToTK(my_flex_form) - - my_flex_form.SetIcon(my_flex_form.WindowIcon) - - try: - root.attributes('-alpha', 255) # hide window while building it. makes for smoother 'paint' - except: - pass - - if my_flex_form.ReturnKeyboardEvents and not my_flex_form.NonBlocking: - root.bind("", my_flex_form._KeyboardCallback) - root.bind("", my_flex_form._MouseWheelCallback) - elif my_flex_form.ReturnKeyboardEvents: - root.bind("", my_flex_form._KeyboardCallback) - root.bind("", my_flex_form._MouseWheelCallback) - - if my_flex_form.AutoClose: - duration = DEFAULT_AUTOCLOSE_TIME if my_flex_form.AutoCloseDuration is None else my_flex_form.AutoCloseDuration - my_flex_form.TKAfterID = root.after(duration * 1000, my_flex_form._AutoCloseAlarmCallback) - if my_flex_form.NonBlocking: - my_flex_form.TKroot.protocol("WM_WINDOW_DESTROYED", my_flex_form.OnClosingCallback()) - else: # it's a blocking form - # print('..... CALLING MainLoop') - my_flex_form.TKroot.mainloop() - # print('..... BACK from MainLoop') - if not my_flex_form.FormRemainedOpen: - _my_windows.Decrement() - if my_flex_form.RootNeedsDestroying: - my_flex_form.TKroot.destroy() - my_flex_form.RootNeedsDestroying = False - return - -# ==============================_GetNumLinesNeeded ==# -# Helper function for determining how to wrap text # -# ===================================================# -def _GetNumLinesNeeded(text, max_line_width): - if max_line_width == 0: - return 1 - lines = text.split('\n') - num_lines = len(lines) # number of original lines of text - max_line_len = max([len(l) for l in lines]) # longest line - lines_used = [] - for L in lines: - lines_used.append(len(L)//max_line_width + (len(L) % max_line_width > 0)) # fancy math to round up - total_lines_needed = sum(lines_used) - return total_lines_needed - -# ============================== PROGRESS METER ========================================== # - -def ConvertArgsToSingleString(*args): - max_line_total, width_used , total_lines, = 0,0,0 - single_line_message = '' - # loop through args and built a SINGLE string from them - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = max(longest_line_len, width_used) - max_line_total = max(max_line_total, width_used) - lines_needed = _GetNumLinesNeeded(message, width_used) - total_lines += lines_needed - single_line_message += message + '\n' - return single_line_message, width_used, total_lines - - -# ============================== ProgressMeter =====# -# ===================================================# -def _ProgressMeter(title, max_value, *args, **_3to2kwargs): - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'border_width' in _3to2kwargs: border_width = _3to2kwargs['border_width']; del _3to2kwargs['border_width'] - else: border_width = None - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = DEFAULT_PROGRESS_BAR_SIZE - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - if 'bar_color' in _3to2kwargs: bar_color = _3to2kwargs['bar_color']; del _3to2kwargs['bar_color'] - else: bar_color = (None,None) - if 'orientation' in _3to2kwargs: orientation = _3to2kwargs['orientation']; del _3to2kwargs['orientation'] - else: orientation = None - ''' - Create and show a form on tbe caller's behalf. - :param title: - :param max_value: - :param args: ANY number of arguments the caller wants to display - :param orientation: - :param bar_color: - :param size: - :param Style: - :param StyleOffset: - :return: ProgressBar object that is in the form - ''' - local_orientation = DEFAULT_METER_ORIENTATION if orientation is None else orientation - local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if border_width is None else border_width - bar2 = ProgressBar(max_value, orientation=local_orientation, size=size, bar_color=bar_color, border_width=local_border_width, relief=DEFAULT_PROGRESS_BAR_RELIEF) - form = Window(title, auto_size_text=True, grab_anywhere=grab_anywhere) - - # Form using a horizontal bar - if local_orientation[0].lower() == 'h': - single_line_message, width, height = ConvertArgsToSingleString(*args) - bar2.TextToDisplay = single_line_message - bar2.MaxValue = max_value - bar2.CurrentValue = 0 - bar_text = Text(single_line_message, size=(width, height + 3), auto_size_text=True) - form.AddRow(bar_text) - form.AddRow((bar2)) - form.AddRow((Cancel(button_color=button_color))) - else: - single_line_message, width, height = ConvertArgsToSingleString(*args) - bar2.TextToDisplay = single_line_message - bar2.MaxValue = max_value - bar2.CurrentValue = 0 - bar_text = Text(single_line_message, size=(width, height + 3), auto_size_text=True) - form.AddRow(bar2, bar_text) - form.AddRow((Cancel(button_color=button_color))) - - form.NonBlocking = True - form.Show(non_blocking= True) - return bar2, bar_text - -# ============================== ProgressMeterUpdate =====# -def _ProgressMeterUpdate(bar, value, text_elem, *args): - ''' - Update the progress meter for a form - :param form: class ProgressBar - :param value: int - :return: True if not cancelled, OK....False if Error - ''' - global _my_windows - if bar == None: return False - if bar.BarExpired: return False - message, w, h = ConvertArgsToSingleString(*args) - text_elem.Update(message) - # bar.TextToDisplay = message - bar.CurrentValue = value - rc = bar.UpdateBar(value) - if value >= bar.MaxValue or not rc: - bar.BarExpired = True - bar.ParentForm._Close() - if rc: # if update was OK but bar expired, decrement num windows - _my_windows.Decrement() - if bar.ParentForm.RootNeedsDestroying: - try: - bar.ParentForm.TKroot.destroy() - # _my_windows.Decrement() - except: pass - bar.ParentForm.RootNeedsDestroying = False - bar.ParentForm.__del__() - return False - - return rc - -# ============================== EASY PROGRESS METER ========================================== # -# class to hold the easy meter info (a global variable essentialy) -class EasyProgressMeterDataClass(object): - def __init__(self, title='', current_value=1, max_value=10, start_time=None, stat_messages=()): - self.Title = title - self.CurrentValue = current_value - self.MaxValue = max_value - self.StartTime = start_time - self.StatMessages = stat_messages - self.ParentForm = None - self.MeterID = None - self.MeterText = None - - # =========================== COMPUTE PROGRESS STATS ======================# - def ComputeProgressStats(self): - utc = datetime.datetime.utcnow() - time_delta = utc - self.StartTime - total_seconds = time_delta.total_seconds() - if not total_seconds: - total_seconds = 1 - try: - time_per_item = total_seconds / self.CurrentValue - except: - time_per_item = 1 - seconds_remaining = (self.MaxValue - self.CurrentValue) * time_per_item - time_remaining = str(datetime.timedelta(seconds=seconds_remaining)) - time_remaining_short = (time_remaining).split(".")[0] - time_delta_short = str(time_delta).split(".")[0] - total_time = time_delta + datetime.timedelta(seconds=seconds_remaining) - total_time_short = str(total_time).split(".")[0] - self.StatMessages = [ - '{} of {}'.format(self.CurrentValue, self.MaxValue), - '{} %'.format(100*self.CurrentValue//self.MaxValue), - '', - ' {:6.2f} Iterations per Second'.format(self.CurrentValue/total_seconds), - ' {:6.2f} Seconds per Iteration'.format(total_seconds/(self.CurrentValue if self.CurrentValue else 1)), - '', - '{} Elapsed Time'.format(time_delta_short), - '{} Time Remaining'.format(time_remaining_short), - '{} Estimated Total Time'.format(total_time_short)] - return - - -# ============================== EasyProgressMeter =====# -def EasyProgressMeter(title, current_value, max_value, *args, **_3to2kwargs): - if 'border_width' in _3to2kwargs: border_width = _3to2kwargs['border_width']; del _3to2kwargs['border_width'] - else: border_width = None - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = DEFAULT_PROGRESS_BAR_SIZE - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - if 'bar_color' in _3to2kwargs: bar_color = _3to2kwargs['bar_color']; del _3to2kwargs['bar_color'] - else: bar_color = (None,None) - if 'orientation' in _3to2kwargs: orientation = _3to2kwargs['orientation']; del _3to2kwargs['orientation'] - else: orientation = None - ''' - A ONE-LINE progress meter. Add to your code where ever you need a meter. No need for a second - function call before your loop. You've got enough code to write! - :param title: Title will be shown on the window - :param current_value: Current count of your items - :param max_value: Max value your count will ever reach. This indicates it should be closed - :param args: VARIABLE number of arguements... you request it, we'll print it no matter what the item! - :param orientation: - :param bar_color: - :param size: - :param Style: - :param StyleOffset: - :return: False if should stop the meter - ''' - local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if not border_width else border_width - # STATIC VARIABLE! - # This is a very clever form of static variable using a function attribute - # If the variable doesn't yet exist, then it will create it and initialize with the 3rd parameter - EasyProgressMeter.Data = getattr(EasyProgressMeter, 'Data', EasyProgressMeterDataClass()) - # if no meter currently running - if EasyProgressMeter.Data.MeterID is None: # Starting a new meter - print("Please change your call of EasyProgressMeter to use OneLineProgressMeter. EasyProgressMeter will be removed soon") - if int(current_value) >= int(max_value): - return False - del(EasyProgressMeter.Data) - EasyProgressMeter.Data = EasyProgressMeterDataClass(title, 1, int(max_value), datetime.datetime.utcnow(), []) - EasyProgressMeter.Data.ComputeProgressStats() - message = "\n".join([line for line in EasyProgressMeter.Data.StatMessages]) - EasyProgressMeter.Data.MeterID, EasyProgressMeter.Data.MeterText= _ProgressMeter(title, int(max_value), message, *args, orientation=orientation, bar_color=bar_color, size=size, button_color=button_color, border_width=local_border_width) - EasyProgressMeter.Data.ParentForm = EasyProgressMeter.Data.MeterID.ParentForm - return True - # if exactly the same values as before, then ignore. - if EasyProgressMeter.Data.MaxValue == max_value and EasyProgressMeter.Data.CurrentValue == current_value: - return True - if EasyProgressMeter.Data.MaxValue != int(max_value): - EasyProgressMeter.Data.MeterID = None - EasyProgressMeter.Data.ParentForm = None - del(EasyProgressMeter.Data) - EasyProgressMeter.Data = EasyProgressMeterDataClass() # setup a new progress meter - return True # HAVE to return TRUE or else the new meter will thing IT is failing when it hasn't - EasyProgressMeter.Data.CurrentValue = int(current_value) - EasyProgressMeter.Data.MaxValue = int(max_value) - EasyProgressMeter.Data.ComputeProgressStats() - message = '' - for line in EasyProgressMeter.Data.StatMessages: - message = message + str(line) + '\n' - message = "\n".join(EasyProgressMeter.Data.StatMessages) - args= args + (message,) - rc = _ProgressMeterUpdate(EasyProgressMeter.Data.MeterID, current_value, - EasyProgressMeter.Data.MeterText, *args) - # if counter >= max then the progress meter is all done. Indicate none running - if current_value >= EasyProgressMeter.Data.MaxValue or not rc: - EasyProgressMeter.Data.MeterID = None - del(EasyProgressMeter.Data) - EasyProgressMeter.Data = EasyProgressMeterDataClass() # setup a new progress meter - return False # even though at the end, return True so don't cause error with the app - return rc # return whatever the update told us - - -def EasyProgressMeterCancel(title, *args): - EasyProgressMeter.EasyProgressMeterData = getattr(EasyProgressMeter, 'EasyProgressMeterData', EasyProgressMeterDataClass()) - if EasyProgressMeter.EasyProgressMeterData.MeterID is not None: - # tell the normal meter update that we're at max value which will close the meter - rc = EasyProgressMeter(title, EasyProgressMeter.EasyProgressMeterData.MaxValue, EasyProgressMeter.EasyProgressMeterData.MaxValue, ' *** CANCELLING ***', 'Caller requested a cancel', *args) - return rc - return True - - -# global variable containing dictionary will all currently running one-line progress meters. -_one_line_progress_meters = {} - -# ============================== OneLineProgressMeter =====# -def OneLineProgressMeter(title, current_value, max_value, key, *args, **_3to2kwargs): - - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'border_width' in _3to2kwargs: border_width = _3to2kwargs['border_width']; del _3to2kwargs['border_width'] - else: border_width = None - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = DEFAULT_PROGRESS_BAR_SIZE - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - if 'bar_color' in _3to2kwargs: bar_color = _3to2kwargs['bar_color']; del _3to2kwargs['bar_color'] - else: bar_color = (None,None) - if 'orientation' in _3to2kwargs: orientation = _3to2kwargs['orientation']; del _3to2kwargs['orientation'] - else: orientation = None - global _one_line_progress_meters - - local_border_width = DEFAULT_PROGRESS_BAR_BORDER_WIDTH if border_width is not None else border_width - try: - meter_data = _one_line_progress_meters[key] - except: # a new meater is starting - if int(current_value) >= int(max_value): # if already expired then it's an old meter, ignore - return False - meter_data = EasyProgressMeterDataClass(title, 1, int(max_value), datetime.datetime.utcnow(), []) - _one_line_progress_meters[key] = meter_data - meter_data.ComputeProgressStats() - message = "\n".join([line for line in meter_data.StatMessages]) - meter_data.MeterID, meter_data.MeterText= _ProgressMeter(title, int(max_value), message, *args, orientation=orientation, bar_color=bar_color, size=size, button_color=button_color, border_width=local_border_width, grab_anywhere=grab_anywhere) - meter_data.ParentForm = meter_data.MeterID.ParentForm - return True - - # if exactly the same values as before, then ignore, return success. - if meter_data.MaxValue == max_value and meter_data.CurrentValue == current_value: - return True - meter_data.CurrentValue = int(current_value) - meter_data.MaxValue = int(max_value) - meter_data.ComputeProgressStats() - message = '' - for line in meter_data.StatMessages: - message = message + str(line) + '\n' - message = "\n".join(meter_data.StatMessages) - args= args + (message,) - rc = _ProgressMeterUpdate(meter_data.MeterID, current_value, - meter_data.MeterText, *args) - # if counter >= max then the progress meter is all done. Indicate none running - if current_value >= meter_data.MaxValue or not rc: - del _one_line_progress_meters[key] - return False - return rc # return whatever the update told us - - -def OneLineProgressMeterCancel(key): - global _one_line_progress_meters - - try: - meter_data = _one_line_progress_meters[key] - except: # meter is already deleted - return - OneLineProgressMeter('', meter_data.MaxValue, meter_data.MaxValue, key=key) - - - - -# input is #RRGGBB -# output is #RRGGBB -def GetComplimentaryHex(color): - # strip the # from the beginning - color = color[1:] - # convert the string into hex - color = int(color, 16) - # invert the three bytes - # as good as substracting each of RGB component by 255(FF) - comp_color = 0xFFFFFF ^ color - # convert the color back to hex by prefixing a # - comp_color = "#%06X" % comp_color - return comp_color - - - -# ======================== EasyPrint =====# -# ===================================================# -_easy_print_data = None # global variable... I'm cheating - -class DebugWin(object): - def __init__(self, size=(None, None)): - # Show a form that's a running counter - win_size = size if size !=(None, None) else DEFAULT_DEBUG_WINDOW_SIZE - self.form = Window('Debug Window', auto_size_text=True, font=('Courier New', 12)) - self.output_element = Output(size=win_size) - self.form_rows = [[Text('EasyPrint Output')], - [self.output_element], - [Quit()]] - self.form.AddRows(self.form_rows) - self.form.Show(non_blocking=True) # Show a ;non-blocking form, returns immediately - return - - def Print(self, *args, **_3to2kwargs): - if 'sep' in _3to2kwargs: sep = _3to2kwargs['sep']; del _3to2kwargs['sep'] - else: sep = None - if 'end' in _3to2kwargs: end = _3to2kwargs['end']; del _3to2kwargs['end'] - else: end = None - sepchar = sep if sep is not None else ' ' - endchar = end if end is not None else '\n' - print(*args, sep=sepchar, end=endchar) - # for a in args: - # msg = str(a) - # print(msg, end="", sep=sepchar) - # print(1, 2, 3, sep='-') - # if end is None: - # print("") - self.form.ReadNonBlocking() - - def Close(self): - self.form.CloseNonBlockingForm() - self.form.__del__() - -def Print(*args, **_3to2kwargs): - if 'sep' in _3to2kwargs: sep = _3to2kwargs['sep']; del _3to2kwargs['sep'] - else: sep = None - if 'end' in _3to2kwargs: end = _3to2kwargs['end']; del _3to2kwargs['end'] - else: end = None - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = (None,None) - EasyPrint(*args, size=size, end=end, sep=sep) - -def PrintClose(): - EasyPrintClose() - -def eprint(*args, **_3to2kwargs): - if 'sep' in _3to2kwargs: sep = _3to2kwargs['sep']; del _3to2kwargs['sep'] - else: sep = None - if 'end' in _3to2kwargs: end = _3to2kwargs['end']; del _3to2kwargs['end'] - else: end = None - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = (None,None) - EasyPrint(*args, size=size, end=end, sep=sep) - -def EasyPrint(*args, **_3to2kwargs): - if 'sep' in _3to2kwargs: sep = _3to2kwargs['sep']; del _3to2kwargs['sep'] - else: sep = None - if 'end' in _3to2kwargs: end = _3to2kwargs['end']; del _3to2kwargs['end'] - else: end = None - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = (None,None) - global _easy_print_data - - if _easy_print_data is None: - _easy_print_data = DebugWin(size=size) - _easy_print_data.Print(*args, end=end, sep=sep) - - - -def EasyPrintold(*args, **_3to2kwargs): - if 'sep' in _3to2kwargs: sep = _3to2kwargs['sep']; del _3to2kwargs['sep'] - else: sep = None - if 'end' in _3to2kwargs: end = _3to2kwargs['end']; del _3to2kwargs['end'] - else: end = None - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = (None,None) - if 'easy_print_data' not in EasyPrint.__dict__: # use a function property to save DebugWin object (static variable) - EasyPrint.easy_print_data = DebugWin(size=size) - if EasyPrint.easy_print_data is None: - EasyPrint.easy_print_data = DebugWin(size=size) - EasyPrint.easy_print_data.Print(*args, end=end, sep=sep) - -def EasyPrintClose(): - if 'easy_print_data' in EasyPrint.__dict__: - if EasyPrint.easy_print_data is not None: - EasyPrint.easy_print_data._Close() - EasyPrint.easy_print_data = None - # del EasyPrint.easy_print_data - -# ======================== Scrolled Text Box =====# -# ===================================================# -def ScrolledTextBox(*args, **_3to2kwargs): - if 'size' in _3to2kwargs: size = _3to2kwargs['size']; del _3to2kwargs['size'] - else: size = (None, None) - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'yes_no' in _3to2kwargs: yes_no = _3to2kwargs['yes_no']; del _3to2kwargs['yes_no'] - else: yes_no = False - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - if not args: return - width, height = size - width = width if width else MESSAGE_BOX_LINE_WIDTH - form = Window(args[0], auto_size_text=True, button_color=button_color, auto_close=auto_close, auto_close_duration=auto_close_duration) - max_line_total, max_line_width, total_lines, height_computed = 0,0,0,0 - complete_output = '' - for message in args: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, width) - max_line_total = max(max_line_total, width_used) - max_line_width = width - lines_needed = _GetNumLinesNeeded(message, width_used) - height_computed += lines_needed - complete_output += message + '\n' - total_lines += lines_needed - height_computed = MAX_SCROLLED_TEXT_BOX_HEIGHT if height_computed > MAX_SCROLLED_TEXT_BOX_HEIGHT else height_computed - if height: - height_computed = height - form.AddRow(Multiline(complete_output, size=(max_line_width, height_computed))) - pad = max_line_total-15 if max_line_total > 15 else 1 - # show either an OK or Yes/No depending on paramater - if yes_no: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Yes(), No()) - button, values = form.Read() - return button - else: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False), Button('OK', size=(5, 1), button_color=button_color)) - button, values = form.Read() - return button - - -PopupScrolled = ScrolledTextBox - -# ---------------------------------------------------------------------- # -# GetPathBox # -# Pre-made dialog that looks like this roughly # -# MESSAGE # -# __________________________ # -# |__________________________| (BROWSE) # -# (SUBMIT) (CANCEL) # -# RETURNS two values: # -# True/False, path # -# (True if Submit was pressed, false otherwise) # -# ---------------------------------------------------------------------- # - -def PopupGetFolder(message, default_path='', no_window=False, size=(None,None), button_color=None, background_color=None, text_color=None, icon=DEFAULT_WINDOW_ICON, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None, None)): - """ - Display popup with text entry field and browse button. Browse for folder - :param message: - :param default_path: - :param no_window: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Contents of text field. None if closed using X - """ - if no_window: - root = tk.Tk() - try: - root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' - except: - pass - folder_name = tk.filedialog.askdirectory() # show the 'get folder' dialog box - root.destroy() - return folder_name - - layout = [[Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=size), FolderBrowse()], - [Ok(), Cancel()]] - - window = Window(title=message, icon=icon, auto_size_text=True, button_color=button_color, background_color=background_color, - font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - (button, input_values) = window.LayoutAndRead(layout) - - if button != 'Ok': - return None - else: - path = input_values[0] - return path - -##################################### -# PopupGetFile # -##################################### -def PopupGetFile(message, default_path='', default_extension='', save_as=False, file_types=(("ALL Files", "*.*"),), no_window=False, size=(None,None), button_color=None, background_color=None, text_color=None, icon=DEFAULT_WINDOW_ICON, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None,None)): - """ - Display popup with text entry field and browse button. Browse for file - - :param message: - :param default_path: - :param save_as: - :param file_types: - :param no_window: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - if no_window: - root = tk.Tk() - try: - root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' - except: - pass - if save_as: - filename = tk.filedialog.asksaveasfilename(filetypes=file_types, defaultextension=default_extension) # show the 'get file' dialog box - else: - filename = tk.filedialog.askopenfilename(filetypes=file_types, defaultextension=default_extension) # show the 'get file' dialog box - root.destroy() - return filename - - browse_button = SaveAs(file_types=file_types) if save_as else FileBrowse(file_types=file_types) - - layout = [[Text(message, auto_size_text=True, text_color=text_color, background_color=background_color)], - [InputText(default_text=default_path, size=size), browse_button], - [Ok(), Cancel()]] - - window = Window(title=message, icon=icon, auto_size_text=True, button_color=button_color, font=font, background_color=background_color, - no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - (button, input_values) = window.Layout(layout).Read() - if button != 'Ok': - return None - else: - path = input_values[0] - return path - -##################################### -# PopupGetText # -##################################### -def PopupGetText(message, default_text='', password_char='', size=(None,None), button_color=None, background_color=None, text_color=None, icon=DEFAULT_WINDOW_ICON, font=None, no_titlebar=False, grab_anywhere=False, keep_on_top=False, location=(None,None)): - """ - Display Popup with text entry field - :param message: - :param default_text: - :param password_char: - :param size: - :param button_color: - :param background_color: - :param text_color: - :param icon: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Text entered or None if window was closed - """ - - layout = [[Text(message, auto_size_text=True, text_color=text_color, background_color=background_color, font=font)], - [InputText(default_text=default_text, size=size, password_char=password_char)], - [Ok(), Cancel()]] - - window = Window(title=message, icon=icon, auto_size_text=True, button_color=button_color, no_titlebar=no_titlebar, - background_color=background_color, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - (button, input_values) = window.Layout(layout).Read() - - if button != 'Ok': - return None - else: - return input_values[0] - - -# ============================== SetGlobalIcon ======# -# Sets the icon to be used by default # -# ===================================================# -def SetGlobalIcon(icon): - global _my_windows - - try: - with open(icon, 'r') as icon_file: - pass - except: - raise FileNotFoundError - _my_windows.user_defined_icon = icon - return True - - -# ============================== SetOptions =========# -# Sets the icon to be used by default # -# ===================================================# -def SetOptions(icon=None, button_color=None, element_size=(None,None), button_element_size=(None, None), margins=(None,None), - element_padding=(None,None),auto_size_text=None, auto_size_buttons=None, font=None, border_width=None, - slider_border_width=None, slider_relief=None, slider_orientation=None, - autoclose_time=None, message_box_line_width=None, - progress_meter_border_depth=None, progress_meter_style=None, - progress_meter_relief=None, progress_meter_color=None, progress_meter_size=None, - text_justification=None, background_color=None, element_background_color=None, - text_element_background_color=None, input_elements_background_color=None, input_text_color=None, - scrollbar_color=None, text_color=None, element_text_color = None, debug_win_size=(None,None), window_location=(None,None), - tooltip_time=None): - - global DEFAULT_ELEMENT_SIZE - global DEFAULT_BUTTON_ELEMENT_SIZE - global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term - global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels - global DEFAULT_AUTOSIZE_TEXT - global DEFAULT_AUTOSIZE_BUTTONS - global DEFAULT_FONT - global DEFAULT_BORDER_WIDTH - global DEFAULT_AUTOCLOSE_TIME - global DEFAULT_BUTTON_COLOR - global MESSAGE_BOX_LINE_WIDTH - global DEFAULT_PROGRESS_BAR_BORDER_WIDTH - global DEFAULT_PROGRESS_BAR_STYLE - global DEFAULT_PROGRESS_BAR_RELIEF - global DEFAULT_PROGRESS_BAR_COLOR - global DEFAULT_PROGRESS_BAR_SIZE - global DEFAULT_TEXT_JUSTIFICATION - global DEFAULT_DEBUG_WINDOW_SIZE - global DEFAULT_SLIDER_BORDER_WIDTH - global DEFAULT_SLIDER_RELIEF - global DEFAULT_SLIDER_ORIENTATION - global DEFAULT_BACKGROUND_COLOR - global DEFAULT_INPUT_ELEMENTS_COLOR - global DEFAULT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR - global DEFAULT_SCROLLBAR_COLOR - global DEFAULT_TEXT_COLOR - global DEFAULT_WINDOW_LOCATION - global DEFAULT_ELEMENT_TEXT_COLOR - global DEFAULT_INPUT_TEXT_COLOR - global DEFAULT_TOOLTIP_TIME - global _my_windows - - if icon: - try: - with open(icon, 'r') as icon_file: - pass - except: - raise FileNotFoundError - _my_windows.user_defined_icon = icon - - if button_color != None: - DEFAULT_BUTTON_COLOR = button_color - - if element_size != (None,None): - DEFAULT_ELEMENT_SIZE = element_size - - if button_element_size != (None,None): - DEFAULT_BUTTON_ELEMENT_SIZE = button_element_size - - if margins != (None,None): - DEFAULT_MARGINS = margins - - if element_padding != (None,None): - DEFAULT_ELEMENT_PADDING = element_padding - - if auto_size_text != None: - DEFAULT_AUTOSIZE_TEXT = auto_size_text - - if auto_size_buttons != None: - DEFAULT_AUTOSIZE_BUTTONS = auto_size_buttons - - if font !=None: - DEFAULT_FONT = font - - if border_width != None: - DEFAULT_BORDER_WIDTH = border_width - - if autoclose_time != None: - DEFAULT_AUTOCLOSE_TIME = autoclose_time - - if message_box_line_width != None: - MESSAGE_BOX_LINE_WIDTH = message_box_line_width - - if progress_meter_border_depth != None: - DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth - - if progress_meter_style != None: - DEFAULT_PROGRESS_BAR_STYLE = progress_meter_style - - if progress_meter_relief != None: - DEFAULT_PROGRESS_BAR_RELIEF = progress_meter_relief - - if progress_meter_color != None: - DEFAULT_PROGRESS_BAR_COLOR = progress_meter_color - - if progress_meter_size != None: - DEFAULT_PROGRESS_BAR_SIZE = progress_meter_size - - if slider_border_width != None: - DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width - - if slider_orientation != None: - DEFAULT_SLIDER_ORIENTATION = slider_orientation - - if slider_relief != None: - DEFAULT_SLIDER_RELIEF = slider_relief - - if text_justification != None: - DEFAULT_TEXT_JUSTIFICATION = text_justification - - if background_color != None: - DEFAULT_BACKGROUND_COLOR = background_color - - if text_element_background_color != None: - DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = text_element_background_color - - if input_elements_background_color != None: - DEFAULT_INPUT_ELEMENTS_COLOR = input_elements_background_color - - if element_background_color != None: - DEFAULT_ELEMENT_BACKGROUND_COLOR = element_background_color - - if window_location != (None,None): - DEFAULT_WINDOW_LOCATION = window_location - - if debug_win_size != (None,None): - DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size - - if text_color != None: - DEFAULT_TEXT_COLOR = text_color - - if scrollbar_color != None: - DEFAULT_SCROLLBAR_COLOR = scrollbar_color - - if element_text_color != None: - DEFAULT_ELEMENT_TEXT_COLOR = element_text_color - - if input_text_color is not None: - DEFAULT_INPUT_TEXT_COLOR = input_text_color - - if tooltip_time is not None: - DEFAULT_TOOLTIP_TIME = tooltip_time - - return True - - -#################### ChangeLookAndFeel ####################### -# Predefined settings that will change the colors and styles # -# of the elements. # -############################################################## -def ChangeLookAndFeel(index): - if sys.platform == 'darwin': - print('*** Changing look and feel is not supported on Mac platform ***') - return - - # look and feel table - look_and_feel = {'SystemDefault': {'BACKGROUND' : COLOR_SYSTEM_DEFAULT, 'TEXT': COLOR_SYSTEM_DEFAULT, 'INPUT': COLOR_SYSTEM_DEFAULT,'TEXT_INPUT' : COLOR_SYSTEM_DEFAULT, 'SCROLL': COLOR_SYSTEM_DEFAULT, 'BUTTON': OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR, 'PROGRESS': COLOR_SYSTEM_DEFAULT, 'BORDER': 1,'SLIDER_DEPTH':1, 'PROGRESS_DEPTH':0}, - - 'GreenTan': {'BACKGROUND' : '#9FB8AD', 'TEXT': COLOR_SYSTEM_DEFAULT, 'INPUT':'#F7F3EC','TEXT_INPUT' : 'black','SCROLL': '#F7F3EC', 'BUTTON': ('white', '#475841'), 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'Dark': {'BACKGROUND': 'gray25', 'TEXT': 'white', 'INPUT': 'gray30', - 'TEXT_INPUT': 'white', 'SCROLL': 'gray44', 'BUTTON': ('white', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'Dark2': {'BACKGROUND': 'gray25', 'TEXT': 'white', 'INPUT': 'white', - 'TEXT_INPUT': 'black', 'SCROLL': 'gray44', 'BUTTON': ('white', '#004F00'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'Black': {'BACKGROUND': 'black', 'TEXT': 'white', 'INPUT': 'gray30', - 'TEXT_INPUT': 'white', 'SCROLL': 'gray44', 'BUTTON': ('black', 'white'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'Tan': {'BACKGROUND': '#fdf6e3', 'TEXT': '#268bd1', 'INPUT': '#eee8d5', - 'TEXT_INPUT': '#6c71c3', 'SCROLL': '#eee8d5', 'BUTTON': ('white', '#063542'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'TanBlue': {'BACKGROUND': '#e5dece', 'TEXT': '#063289', 'INPUT': '#f9f8f4', - 'TEXT_INPUT': '#242834', 'SCROLL': '#eee8d5', 'BUTTON': ('white', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'DarkTanBlue': {'BACKGROUND': '#242834', 'TEXT': '#dfe6f8', 'INPUT': '#97755c', - 'TEXT_INPUT': 'white', 'SCROLL': '#a9afbb', 'BUTTON': ('white', '#063289'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'DarkAmber': {'BACKGROUND': '#2c2825', 'TEXT': '#fdcb52', 'INPUT': '#705e52', - 'TEXT_INPUT': '#fdcb52', 'SCROLL': '#705e52', 'BUTTON': ('black', '#fdcb52'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'DarkBlue': {'BACKGROUND': '#1a2835', 'TEXT': '#d1ecff', 'INPUT': '#335267', - 'TEXT_INPUT': '#acc2d0', 'SCROLL': '#1b6497', 'BUTTON': ('black', '#fafaf8'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'Reds': {'BACKGROUND': '#280001', 'TEXT': 'white', 'INPUT': '#d8d584', - 'TEXT_INPUT': 'black', 'SCROLL': '#763e00', 'BUTTON': ('black', '#daad28'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'Green': {'BACKGROUND': '#82a459', 'TEXT': 'black', 'INPUT': '#d8d584', - 'TEXT_INPUT': 'black', 'SCROLL': '#e3ecf3', 'BUTTON': ('white', '#517239'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1, 'SLIDER_DEPTH': 0, - 'PROGRESS_DEPTH': 0}, - - 'LightGreen' :{'BACKGROUND' : '#B7CECE', 'TEXT': 'black', 'INPUT':'#FDFFF7','TEXT_INPUT' : 'black', 'SCROLL': '#FDFFF7','BUTTON': ('white', '#658268'), 'PROGRESS':DEFAULT_PROGRESS_BAR_COLOR, 'BORDER':1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'BluePurple': {'BACKGROUND' : '#A5CADD', 'TEXT': '#6E266E', 'INPUT':'#E0F5FF','TEXT_INPUT' : 'black', 'SCROLL': '#E0F5FF','BUTTON': ('white', '#303952'),'PROGRESS':DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'Purple': {'BACKGROUND': '#B0AAC2', 'TEXT': 'black', 'INPUT': '#F2EFE8','SCROLL': '#F2EFE8','TEXT_INPUT' : 'black', - 'BUTTON': ('black', '#C2D4D8'), 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'BlueMono': {'BACKGROUND': '#AAB6D3', 'TEXT': 'black', 'INPUT': '#F1F4FC','SCROLL': '#F1F4FC','TEXT_INPUT' : 'black', - 'BUTTON': ('white', '#7186C7'), 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'GreenMono': {'BACKGROUND': '#A8C1B4', 'TEXT': 'black', 'INPUT': '#DDE0DE', 'SCROLL': '#E3E3E3','TEXT_INPUT' : 'black', - 'BUTTON': ('white', '#6D9F85'), 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'BrownBlue': {'BACKGROUND': '#64778d', 'TEXT': 'white', 'INPUT': '#f0f3f7', 'SCROLL': '#A6B2BE','TEXT_INPUT' : 'black', 'BUTTON': ('white', '#283b5b'), 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'BrightColors': {'BACKGROUND': '#b4ffb4', 'TEXT': 'black', 'INPUT': '#ffff64','SCROLL': '#ffb482','TEXT_INPUT' : 'black', 'BUTTON': ('black', '#ffa0dc'), 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'NeutralBlue': {'BACKGROUND': '#92aa9d', 'TEXT': 'black', 'INPUT': '#fcfff6', - 'SCROLL': '#fcfff6', 'TEXT_INPUT': 'black', 'BUTTON': ('black', '#d0dbbd'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'Kayak': {'BACKGROUND': '#a7ad7f', 'TEXT': 'black', 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', 'TEXT_INPUT': 'black', 'BUTTON': ('white', '#5d907d'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'SandyBeach': {'BACKGROUND': '#efeccb', 'TEXT': '#012f2f', 'INPUT': '#e6d3a8', - 'SCROLL': '#e6d3a8', 'TEXT_INPUT': '#012f2f', 'BUTTON': ('white', '#046380'), - 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0}, - - 'TealMono': {'BACKGROUND': '#a8cfdd', 'TEXT': 'black', 'INPUT': '#dfedf2','SCROLL': '#dfedf2', 'TEXT_INPUT' : 'black', 'BUTTON': ('white', '#183440'), 'PROGRESS': DEFAULT_PROGRESS_BAR_COLOR, 'BORDER': 1,'SLIDER_DEPTH':0, 'PROGRESS_DEPTH':0} - } - try: - colors = look_and_feel[index] - - SetOptions(background_color=colors['BACKGROUND'], - text_element_background_color=colors['BACKGROUND'], - element_background_color=colors['BACKGROUND'], - text_color=colors['TEXT'], - input_elements_background_color=colors['INPUT'], - button_color=colors['BUTTON'], - progress_meter_color=colors['PROGRESS'], - border_width=colors['BORDER'], - slider_border_width=colors['SLIDER_DEPTH'], - progress_meter_border_depth=colors['PROGRESS_DEPTH'], - scrollbar_color=(colors['SCROLL']), - element_text_color=colors['TEXT'], - input_text_color=colors['TEXT_INPUT']) - except: # most likely an index out of range - pass - - - - -# ============================== sprint ======# -# Is identical to the Scrolled Text Box # -# Provides a crude 'print' mechanism but in a # -# GUI environment # -# ============================================# -sprint=ScrolledTextBox - -# Converts an object's contents into a nice printable string. Great for dumping debug data -def ObjToStringSingleObj(obj): - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join( - (repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) - -def ObjToString(obj, extra=' '): - if obj is None: - return 'None' - return str(obj.__class__) + '\n' + '\n'.join( - (extra + (str(item) + ' = ' + - (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str( - obj.__dict__[item]))) - for item in sorted(obj.__dict__))) - - -# ------------------------------------------------------------------------------------------------------------------ # -# ===================================== Upper PySimpleGUI ======================================================== # -# Pre-built dialog boxes for all your needs These are the "high level API calls # -# ------------------------------------------------------------------------------------------------------------------ # - -# ----------------------------------- The mighty Popup! ------------------------------------------------------------ # - -def Popup(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'button_type' in _3to2kwargs: button_type = _3to2kwargs['button_type']; del _3to2kwargs['button_type'] - else: button_type = POPUP_BUTTONS_OK - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - """ - Popup - Display a popup box with as many parms as you wish to include - :param args: - :param button_color: - :param background_color: - :param text_color: - :param button_type: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - if not args: - args_to_print = [''] - else: - args_to_print = args - if line_width != None: - local_line_width = line_width - else: - local_line_width = MESSAGE_BOX_LINE_WIDTH - title = args_to_print[0] if args_to_print[0] is not None else 'None' - form = Window(title, auto_size_text=True, background_color=background_color, button_color=button_color, auto_close=auto_close, auto_close_duration=auto_close_duration, icon=icon, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - max_line_total, total_lines = 0,0 - for message in args_to_print: - # fancy code to check if string and convert if not is not need. Just always convert to string :-) - # if not isinstance(message, str): message = str(message) - message = str(message) - if message.count('\n'): - message_wrapped = message - else: - message_wrapped = textwrap.fill(message, local_line_width) - message_wrapped_lines = message_wrapped.count('\n')+1 - longest_line_len = max([len(l) for l in message.split('\n')]) - width_used = min(longest_line_len, local_line_width) - max_line_total = max(max_line_total, width_used) - # height = _GetNumLinesNeeded(message, width_used) - height = message_wrapped_lines - form.AddRow(Text(message_wrapped, auto_size_text=True, text_color=text_color, background_color=background_color)) - total_lines += height - - pad = max_line_total-15 if max_line_total > 15 else 1 - pad =1 - if non_blocking: - PopupButton = DummyButton - else: - PopupButton = SimpleButton - # show either an OK or Yes/No depending on paramater - if button_type is POPUP_BUTTONS_YES_NO: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False, text_color=text_color, background_color=background_color), PopupButton('Yes', button_color=button_color, focus=True, bind_return_key=True), PopupButton('No', button_color=button_color)) - elif button_type is POPUP_BUTTONS_CANCELLED: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False, text_color=text_color, background_color=background_color), PopupButton('Cancelled', button_color=button_color, focus=True, bind_return_key=True)) - elif button_type is POPUP_BUTTONS_ERROR: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False, text_color=text_color, background_color=background_color), PopupButton('Error', size=(6, 1), button_color=button_color, focus=True, bind_return_key=True)) - elif button_type is POPUP_BUTTONS_OK_CANCEL: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False, text_color=text_color, background_color=background_color), PopupButton('OK', size=(5, 1), button_color=button_color, focus=True, bind_return_key=True), - PopupButton('Cancel', size=(5, 1), button_color=button_color)) - elif button_type is POPUP_BUTTONS_NO_BUTTONS: - pass - else: - form.AddRow(Text('', size=(pad, 1), auto_size_text=False, background_color=background_color), PopupButton('OK', size=(5, 1), button_color=button_color, focus=True, bind_return_key=True)) - - if non_blocking: - button, values = form.ReadNonBlocking() - else: - button, values = form.Show() - - return button - - - -# ============================== MsgBox============# -# Lazy function. Same as calling Popup with parms # -# This function WILL Disappear perhaps today # -# ==================================================# -# MsgBox is the legacy call and should not be used any longer -def MsgBox(*args): - raise DeprecationWarning('MsgBox is no longer supported... change your call to Popup') - - -# --------------------------- PopupNoButtons --------------------------- -def PopupNoButtons(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - """ - Show a Popup but without any buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=POPUP_BUTTONS_NO_BUTTONS, - auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, - font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -# --------------------------- PopupNonBlocking --------------------------- -def PopupNonBlocking(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = True - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - if 'button_type' in _3to2kwargs: button_type = _3to2kwargs['button_type']; del _3to2kwargs['button_type'] - else: button_type = POPUP_BUTTONS_OK - """ - Show Popup box and immediately return (does not block) - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, - auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, - font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -PopupNoWait = PopupNonBlocking - - -# --------------------------- PopupNoTitlebar --------------------------- -def PopupNoTitlebar(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = True - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - if 'button_type' in _3to2kwargs: button_type = _3to2kwargs['button_type']; del _3to2kwargs['button_type'] - else: button_type = POPUP_BUTTONS_OK - """ - Display a Popup without a titlebar. Enables grab anywhere so you can move it - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, - auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, - font=font, no_titlebar=True, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - -PopupNoFrame = PopupNoTitlebar -PopupNoBorder = PopupNoTitlebar -PopupAnnoying = PopupNoTitlebar - - -# --------------------------- PopupAutoClose --------------------------- -def PopupAutoClose(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = True - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - if 'button_type' in _3to2kwargs: button_type = _3to2kwargs['button_type']; del _3to2kwargs['button_type'] - else: button_type = POPUP_BUTTONS_OK - """ - Popup that closes itself after some time period - :param args: - :param button_type: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_color=button_color, background_color=background_color, text_color=text_color, button_type=button_type, - auto_close=auto_close, auto_close_duration=auto_close_duration, non_blocking=non_blocking, icon=icon, line_width=line_width, - font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - -PopupTimed = PopupAutoClose - -# --------------------------- PopupError --------------------------- -def PopupError(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = DEFAULT_ERROR_BUTTON_COLOR - """ - Popup with colored button and 'Error' as button text - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_type=POPUP_BUTTONS_ERROR, background_color=background_color, text_color=text_color, non_blocking=non_blocking, icon=icon, line_width=line_width, button_color=button_color, auto_close=auto_close, auto_close_duration=auto_close_duration, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -# --------------------------- PopupCancel --------------------------- -def PopupCancel(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - """ - Display Popup with "cancelled" button text - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_type=POPUP_BUTTONS_CANCELLED, background_color=background_color, text_color=text_color, non_blocking=non_blocking, icon=icon, line_width=line_width, button_color=button_color, auto_close=auto_close, auto_close_duration=auto_close_duration, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - -# --------------------------- PopupOK --------------------------- -def PopupOK(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - """ - Display Popup with OK button only - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: - """ - Popup(*args, button_type=POPUP_BUTTONS_OK, background_color=background_color, text_color=text_color, non_blocking=non_blocking, icon=icon, line_width=line_width, button_color=button_color, auto_close=auto_close, auto_close_duration=auto_close_duration, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - -# --------------------------- PopupOKCancel --------------------------- -def PopupOKCancel(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - """ - Display popup with OK and Cancel buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: OK, Cancel or None - """ - return Popup(*args, button_type=POPUP_BUTTONS_OK_CANCEL, background_color=background_color, text_color=text_color, non_blocking=non_blocking, icon=icon, line_width=line_width, button_color=button_color, auto_close=auto_close, auto_close_duration=auto_close_duration, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - -# --------------------------- PopupYesNo --------------------------- -def PopupYesNo(*args, **_3to2kwargs): - if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location'] - else: location = (None,None) - if 'keep_on_top' in _3to2kwargs: keep_on_top = _3to2kwargs['keep_on_top']; del _3to2kwargs['keep_on_top'] - else: keep_on_top = False - if 'grab_anywhere' in _3to2kwargs: grab_anywhere = _3to2kwargs['grab_anywhere']; del _3to2kwargs['grab_anywhere'] - else: grab_anywhere = False - if 'no_titlebar' in _3to2kwargs: no_titlebar = _3to2kwargs['no_titlebar']; del _3to2kwargs['no_titlebar'] - else: no_titlebar = False - if 'font' in _3to2kwargs: font = _3to2kwargs['font']; del _3to2kwargs['font'] - else: font = None - if 'line_width' in _3to2kwargs: line_width = _3to2kwargs['line_width']; del _3to2kwargs['line_width'] - else: line_width = None - if 'icon' in _3to2kwargs: icon = _3to2kwargs['icon']; del _3to2kwargs['icon'] - else: icon = DEFAULT_WINDOW_ICON - if 'non_blocking' in _3to2kwargs: non_blocking = _3to2kwargs['non_blocking']; del _3to2kwargs['non_blocking'] - else: non_blocking = False - if 'auto_close_duration' in _3to2kwargs: auto_close_duration = _3to2kwargs['auto_close_duration']; del _3to2kwargs['auto_close_duration'] - else: auto_close_duration = None - if 'auto_close' in _3to2kwargs: auto_close = _3to2kwargs['auto_close']; del _3to2kwargs['auto_close'] - else: auto_close = False - if 'text_color' in _3to2kwargs: text_color = _3to2kwargs['text_color']; del _3to2kwargs['text_color'] - else: text_color = None - if 'background_color' in _3to2kwargs: background_color = _3to2kwargs['background_color']; del _3to2kwargs['background_color'] - else: background_color = None - if 'button_color' in _3to2kwargs: button_color = _3to2kwargs['button_color']; del _3to2kwargs['button_color'] - else: button_color = None - """ - Display Popup with Yes and No buttons - :param args: - :param button_color: - :param background_color: - :param text_color: - :param auto_close: - :param auto_close_duration: - :param non_blocking: - :param icon: - :param line_width: - :param font: - :param no_titlebar: - :param grab_anywhere: - :param keep_on_top: - :param location: - :return: Yes, No or None - """ - return Popup(*args, button_type=POPUP_BUTTONS_YES_NO, background_color=background_color, text_color=text_color, non_blocking=non_blocking, icon=icon, line_width=line_width, button_color=button_color, auto_close=auto_close, auto_close_duration=auto_close_duration, font=font, no_titlebar=no_titlebar, grab_anywhere=grab_anywhere, keep_on_top=keep_on_top, location=location) - - -def main(): - window = Window('Demo window..') - window_rows = [[Text('You are running the PySimpleGUI.py file itself')], - [Text('You should be importing it rather than running it', size=(50,2))], - [Text('Here is your sample input window....')], - [Text('Source Folder', size=(15, 1), justification='right'), InputText('Source', focus=True),FolderBrowse()], - [Text('Destination Folder', size=(15, 1), justification='right'), InputText('Dest'), FolderBrowse()], - [Ok(), Cancel()]] - - button, (source, dest) = window.LayoutAndRead(window_rows) - - -if __name__ == '__main__': - main() - exit(69) \ No newline at end of file diff --git a/docs/cookbook.md b/docs/cookbook.md index 96b56fd9..bc44c835 100644 --- a/docs/cookbook.md +++ b/docs/cookbook.md @@ -1,1284 +1,1285 @@ -![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) - -# The PySimpleGUI Cookbook - - -You'll find that starting with a Recipe will give you a big jump-start on creating your custom GUI. Copy and paste one of these Recipes and modify it to match your requirements. Study them to get an idea of what design patterns to follow. - -The Recipes in this Cookbook all assume you're running on a Python3 machine. If you are running Python 2.7 then your code will differ by 2 character. Replace the import statement: - - import PySimpleGUI as sg - -with - - import PySimpleGUI27 as sg - -There is a short section in the Readme with instruction on installing PySimpleGUI - - -## Simple Data Entry - Return Values As List -Same GUI screen except the return values are in a list instead of a dictionary and doesn't have initial values. - -![super simple 2](https://user-images.githubusercontent.com/13696193/43934091-8100e29a-9c1b-11e8-8d0a-9bd2d13e6d8e.jpg) - - import PySimpleGUI as sg - - # Very basic window. Return values as a list - - layout = [ - [sg.Text('Please enter your Name, Address, Phone')], - [sg.Text('Name', size=(15, 1)), sg.InputText()], - [sg.Text('Address', size=(15, 1)), sg.InputText()], - [sg.Text('Phone', size=(15, 1)), sg.InputText()], - [sg.Submit(), sg.Cancel()] - ] - - window = sg.Window('Simple data entry window').Layout(layout) - button, values = window.Read() - - print(button, values[0], values[1], values[2]) - -## Simple data entry - Return Values As Dictionary -A simple GUI with default values. Results returned in a dictionary. - -![super simple 2](https://user-images.githubusercontent.com/13696193/43934091-8100e29a-9c1b-11e8-8d0a-9bd2d13e6d8e.jpg) - - import PySimpleGUI as sg - - # Very basic window. Return values as a dictionary - - layout = [ - [sg.Text('Please enter your Name, Address, Phone')], - [sg.Text('Name', size=(15, 1)), sg.InputText('name', key='name')], - [sg.Text('Address', size=(15, 1)), sg.InputText('address', key='address')], - [sg.Text('Phone', size=(15, 1)), sg.InputText('phone', key='phone')], - [sg.Submit(), sg.Cancel()] - ] - - window = sg.Window('Simple data entry GUI').Layout(layout) - - button, values = window.Read() - - print(button, values['name'], values['address'], values['phone']) - ---------------------- - - - ------------ -## Simple File Browse -Browse for a filename that is populated into the input field. - -![simple file browse](https://user-images.githubusercontent.com/13696193/43934539-d8bd9490-9c1d-11e8-927f-98b523776fcb.jpg) - - import PySimpleGUI as sg - - GUI_rows = [[sg.Text('SHA-1 and SHA-256 Hashes for the file')], - [sg.InputText(), sg.FileBrowse()], - [sg.Submit(), sg.Cancel()]] - - (button, (source_filename,)) = sg.Window('SHA-1 & 256 Hash').Layout(GUI_rows).Read() - - print(button, source_filename) - --------------------------- -## Add GUI to Front-End of Script -Quickly add a GUI allowing the user to browse for a filename if a filename is not supplied on the command line using this 1-line GUI. It's the best of both worlds. - -![script front-end](https://user-images.githubusercontent.com/13696193/44756573-39e9c380-aaf9-11e8-97b4-6679f9f5bd46.jpg) - - - import PySimpleGUI as sg - import sys - - if len(sys.argv) == 1: - button, (fname,) = sg.Window('My Script').Layout([[sg.Text('Document to open')], - [sg.In(), sg.FileBrowse()], - [sg.Open(), sg.Cancel()]]).Read() - else: - fname = sys.argv[1] - - if not fname: - sg.Popup("Cancel", "No filename supplied") - raise SystemExit("Cancelling: no filename supplied") - print(button, fname) - - - --------------- - -## Compare 2 Files - -Browse to get 2 file names that can be then compared. - -![compare 2 files](https://user-images.githubusercontent.com/13696193/43934659-60dc5fbe-9c1e-11e8-8d2b-07c0e3b61892.jpg) - - import PySimpleGUI as sg - - gui_rows = [[sg.Text('Enter 2 files to comare')], - [sg.Text('File 1', size=(8, 1)), sg.InputText(), sg.FileBrowse()], - [sg.Text('File 2', size=(8, 1)), sg.InputText(), sg.FileBrowse()], - [sg.Submit(), sg.Cancel()]] - - window = sg.Window('File Compare').Layout(gui_rows) - - button, values = window.Read() - - print(button, values) - ---------------- -## Nearly All Widgets with Green Color Theme -Example of nearly all of the widgets in a single window. Uses a customized color scheme. - -![latest everything bagel](https://user-images.githubusercontent.com/13696193/45920376-22d89000-be71-11e8-8ac4-640f011f84d0.jpg) - - - - #!/usr/bin/env Python3 - import PySimpleGUI as sg - - sg.ChangeLookAndFeel('GreenTan') - - # ------ Menu Definition ------ # - menu_def = [['File', ['Open', 'Save', 'Exit', 'Properties']], - ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], - ['Help', 'About...'], ] - - # ------ Column Definition ------ # - column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]] - - layout = [ - [sg.Menu(menu_def, tearoff=True)], - [sg.Text('All graphic widgets in one window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)], - [sg.Text('Here is some text.... and a place to enter text')], - [sg.InputText('This is my text')], - [sg.Frame(layout=[ - [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)], - [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')], - [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)), - sg.Multiline(default_text='A second multi-line', size=(35, 3))], - [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)), - sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)], - [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))], - [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)), - sg.Frame('Labelled Group',[[ - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25), - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75), - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10), - sg.Column(column1, background_color='#F7F3EC')]])], - [sg.Text('_' * 80)], - [sg.Text('Choose A Folder', size=(35, 1))], - [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'), - sg.InputText('Default Folder'), sg.FolderBrowse()], - [sg.Submit(tooltip='Click to submit this window'), sg.Cancel()] - ] - - - window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout) - - button, values = window.Read() - - sg.Popup('Title', - 'The results of the window.', - 'The button clicked was "{}"'.format(button), - 'The values are', values) - -------------- - - - - -## Non-Blocking Window With Periodic Update -An async Window that has a button read loop. A Text Element is updated periodically with a running timer. Note that `value` is checked for None which indicates the window was closed using X. - -![non-blocking](https://user-images.githubusercontent.com/13696193/43955295-70f6ac48-9c6d-11e8-8ea2-e6729ba9330c.jpg) - - - import PySimpleGUI as sg - import time - - gui_rows = [[sg.Text('Stopwatch', size=(20, 2), justification='center')], - [sg.Text('', size=(10, 2), font=('Helvetica', 20), justification='center', key='output')], - [sg.T(' ' * 5), sg.ReadButton('Start/Stop', focus=True), sg.Quit()]] - - window = sg.Window('Running Timer').Layout(gui_rows) - - timer_running = True - i = 0 - # Event Loop - while True: - i += 1 * (timer_running is True) - button, values = window.ReadNonBlocking() - - if values is None or button == 'Quit': # if user closed the window using X or clicked Quit button - break - elif button == 'Start/Stop': - timer_running = not timer_running - - window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format((i // 100) // 60, (i // 100) % 60, i % 100)) - time.sleep(.01) - --------- - -## Callback Function Simulation -The architecture of some programs works better with button callbacks instead of handling in-line. While button callbacks are part of the PySimpleGUI implementation, they are not directly exposed to the caller. The way to get the same result as callbacks is to simulate them with a recipe like this one. - -![button callback 2](https://user-images.githubusercontent.com/13696193/43955588-e139ddc6-9c6e-11e8-8c78-c1c226b8d9b1.jpg) - - import PySimpleGUI as sg - - # This design pattern simulates button callbacks - # Note that callbacks are NOT a part of the package's interface to the - # caller intentionally. The underlying implementation actually does use - # tkinter callbacks. They are simply hidden from the user. - - # The callback functions - def button1(): - print('Button 1 callback') - - def button2(): - print('Button 2 callback') - - - # Layout the design of the GUI - layout = [[sg.Text('Please click a button', auto_size_text=True)], - [sg.ReadButton('1'), sg.ReadButton('2'), sg.Quit()]] - - # Show the Window to the user - window = sg.Window('Button callback example').Layout(layout) - - # Event loop. Read buttons, make callbacks - while True: - # Read the Window - button, value = window.Read() - # Take appropriate action based on button - if button == '1': - button1() - elif button == '2': - button2() - elif button =='Quit' or button is None: - break - - # All done! - sg.PopupOK('Done') - ------ -## Realtime Buttons (Good For Raspberry Pi) -This recipe implements a remote control interface for a robot. There are 4 directions, forward, reverse, left, right. When a button is clicked, PySimpleGUI immediately returns button events for as long as the buttons is held down. When released, the button events stop. This is an async/non-blocking window. - -![robot control](https://user-images.githubusercontent.com/13696193/44006710-d227f23e-9e56-11e8-89a3-2be5b2726199.jpg) - - import PySimpleGUI as sg - - - gui_rows = [[sg.Text('Robotics Remote Control')], - [sg.T(' ' * 10), sg.RealtimeButton('Forward')], - [sg.RealtimeButton('Left'), sg.T(' ' * 15), sg.RealtimeButton('Right')], - [sg.T(' ' * 10), sg.RealtimeButton('Reverse')], - [sg.T('')], - [sg.Quit(button_color=('black', 'orange'))] - ] - - window = sg.Window('Robotics Remote Control', auto_size_text=True).Layout(gui_rows) - - # - # Some place later in your code... - # You need to perform a ReadNonBlocking on your window every now and then or - # else it won't refresh. - # - # your program's main loop - while (True): - # This is the code that reads and updates your window - button, values = window.ReadNonBlocking() - if button is not None: - print(button) - if button == 'Quit' or values is None: - break - - window.CloseNonBlocking() # Don't forget to close your window! - ---------- - -## OneLineProgressMeter - -This recipe shows just how easy it is to add a progress meter to your code. - -![onelineprogressmeter](https://user-images.githubusercontent.com/13696193/45589254-bd285900-b8f0-11e8-9122-b43f06bf074d.jpg) - - - import PySimpleGUI as sg - - for i in range(1000): - sg.OneLineProgressMeter('One Line Meter Example', i+1, 1000, 'key') - - - ------ -## Button Graphics (Media Player) -Buttons can have PNG of GIF images on them. This Media Player recipe requires 4 images in order to function correctly. The background is set to the same color as the button background so that they blend together. - -![media player](https://user-images.githubusercontent.com/13696193/43958418-5dd133f2-9c79-11e8-9432-0a67007e85ac.jpg) - - import PySimpleGUI as sg - - background = '#F0F0F0' - # Set the backgrounds the same as the background on the buttons - sg.SetOptions(background_color=background, element_background_color=background) - # Images are located in a subfolder in the Demo Media Player.py folder - image_pause = './ButtonGraphics/Pause.png' - image_restart = './ButtonGraphics/Restart.png' - image_next = './ButtonGraphics/Next.png' - image_exit = './ButtonGraphics/Exit.png' - - # define layout of the rows - layout = [[sg.Text('Media File Player', size=(17, 1), font=("Helvetica", 25))], - [sg.Text('', size=(15, 2), font=("Helvetica", 14), key='output')], - [sg.ReadButton('Restart Song', button_color=(background, background), - image_filename=image_restart, image_size=(50, 50), image_subsample=2, border_width=0), - sg.Text(' ' * 2), - sg.ReadButton('Pause', button_color=(background, background), - image_filename=image_pause, image_size=(50, 50), image_subsample=2, border_width=0), - sg.Text(' ' * 2), - sg.ReadButton('Next', button_color=(background, background), - image_filename=image_next, image_size=(50, 50), image_subsample=2, border_width=0), - sg.Text(' ' * 2), - sg.Text(' ' * 2), sg.Button('Exit', button_color=(background, background), - image_filename=image_exit, image_size=(50, 50), image_subsample=2, - border_width=0)], - [sg.Text('_' * 30)], - [sg.Text(' ' * 30)], - [ - sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', - font=("Helvetica", 15)), - sg.Text(' ' * 2), - sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', - font=("Helvetica", 15)), - sg.Text(' ' * 8), - sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', - font=("Helvetica", 15))], - [sg.Text('Bass', font=("Helvetica", 15), size=(6, 1)), - sg.Text('Treble', font=("Helvetica", 15), size=(10, 1)), - sg.Text('Volume', font=("Helvetica", 15), size=(7, 1))] - ] - - window = sg.Window('Media File Player', auto_size_text=True, default_element_size=(20, 1), - font=("Helvetica", 25)).Layout(layout) - # Our event loop - while (True): - # Read the window (this call will not block) - button, values = window.ReadNonBlocking() - if button == 'Exit' or values is None: - break - # If a button was pressed, display it on the GUI by updating the text element - if button: - window.FindElement('output').Update(button) - ----- -## Script Launcher - Persistent Window -This Window doesn't close after button clicks. To achieve this the buttons are specified as `sg.ReadButton` instead of `sg.Button`. The exception to this is the EXIT button. Clicking it will close the window. This program will run commands and display the output in the scrollable window. - -![launcher 2](https://user-images.githubusercontent.com/13696193/43958519-b30af218-9c79-11e8-88da-fadc69da818c.jpg) - - import PySimpleGUI as sg - import subprocess - - # Please check Demo programs for better examples of launchers - def ExecuteCommandSubprocess(command, *args): - try: - sp = subprocess.Popen([command, *args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = sp.communicate() - if out: - print(out.decode("utf-8")) - if err: - print(err.decode("utf-8")) - except: - pass - - - layout = [ - [sg.Text('Script output....', size=(40, 1))], - [sg.Output(size=(88, 20))], - [sg.ReadButton('script1'), sg.ReadButton('script2'), sg.Button('EXIT')], - [sg.Text('Manual command', size=(15, 1)), sg.InputText(focus=True), sg.ReadButton('Run', bind_return_key=True)] - ] - - - window = sg.Window('Script launcher').Layout(layout) - - # ---===--- Loop taking in user input and using it to call scripts --- # - - while True: - (button, value) = window.Read() - if button == 'EXIT' or button is None: - break # exit button clicked - if button == 'script1': - ExecuteCommandSubprocess('pip', 'list') - elif button == 'script2': - ExecuteCommandSubprocess('python', '--version') - elif button == 'Run': - ExecuteCommandSubprocess(value[0]) - ----- -## Machine Learning GUI -A standard non-blocking GUI with lots of inputs. - -![machine learning green](https://user-images.githubusercontent.com/13696193/43979000-408b77ba-9cb7-11e8-9ffd-24c156767532.jpg) - - import PySimpleGUI as sg - - # Green & tan color scheme - sg.ChangeLookAndFeel('GreenTan') - - sg.SetOptions(text_justification='right') - - layout = [[sg.Text('Machine Learning Command Line Parameters', font=('Helvetica', 16))], - [sg.Text('Passes', size=(15, 1)), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1)), - sg.Text('Steps', size=(18, 1)), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1))], - [sg.Text('ooa', size=(15, 1)), sg.In(default_text='6', size=(10, 1)), sg.Text('nn', size=(15, 1)), - sg.In(default_text='10', size=(10, 1))], - [sg.Text('q', size=(15, 1)), sg.In(default_text='ff', size=(10, 1)), sg.Text('ngram', size=(15, 1)), - sg.In(default_text='5', size=(10, 1))], - [sg.Text('l', size=(15, 1)), sg.In(default_text='0.4', size=(10, 1)), sg.Text('Layers', size=(15, 1)), - sg.Drop(values=('BatchNorm', 'other'), auto_size_text=True)], - [sg.Text('_' * 100, size=(65, 1))], - [sg.Text('Flags', font=('Helvetica', 15), justification='left')], - [sg.Checkbox('Normalize', size=(12, 1), default=True), sg.Checkbox('Verbose', size=(20, 1))], - [sg.Checkbox('Cluster', size=(12, 1)), sg.Checkbox('Flush Output', size=(20, 1), default=True)], - [sg.Checkbox('Write Results', size=(12, 1)), sg.Checkbox('Keep Intermediate Data', size=(20, 1))], - [sg.Text('_' * 100, size=(65, 1))], - [sg.Text('Loss Functions', font=('Helvetica', 15), justification='left')], - [sg.Radio('Cross-Entropy', 'loss', size=(12, 1)), sg.Radio('Logistic', 'loss', default=True, size=(12, 1))], - [sg.Radio('Hinge', 'loss', size=(12, 1)), sg.Radio('Huber', 'loss', size=(12, 1))], - [sg.Radio('Kullerback', 'loss', size=(12, 1)), sg.Radio('MAE(L1)', 'loss', size=(12, 1))], - [sg.Radio('MSE(L2)', 'loss', size=(12, 1)), sg.Radio('MB(L0)', 'loss', size=(12, 1))], - [sg.Submit(), sg.Cancel()]] - - window = sg.Window('Machine Learning Front End', font=("Helvetica", 12)).Layout(layout) - - button, values = window.Read() - -------- -## Custom Progress Meter / Progress Bar -Perhaps you don't want all the statistics that the EasyProgressMeter provides and want to create your own progress bar. Use this recipe to do just that. - -![custom progress meter](https://user-images.githubusercontent.com/13696193/43982958-3393b23e-9cc6-11e8-8b49-e7f4890cbc4b.jpg) - - - import PySimpleGUI as sg - - # layout the Window - layout = [[sg.Text('A custom progress meter')], - [sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progbar')], - [sg.Cancel()]] - - # create the Window - window = sg.Window('Custom Progress Meter').Layout(layout) - # loop that would normally do something useful - for i in range(10000): - # check to see if the cancel button was clicked and exit loop if clicked - button, values = window.ReadNonBlocking() - if button == 'Cancel' or values == None: - break - # update bar with loop value +1 so that bar eventually reaches the maximum - window.FindElement('progbar').UpdateBar(i + 1) - # done with loop... need to destroy the window as it's still open - window.CloseNonBlocking() - - - ---- - -## The One-Line GUI - -For those of you into super-compact code, a complete customized GUI can be specified, shown, and received the results using a single line of Python code. - - -![simple](https://user-images.githubusercontent.com/13696193/44227935-ecb53b80-a161-11e8-968b-b3f963404dec.jpg) - - -Instead of - - import PySimpleGUI as sg - - layout = [[sg.Text('Filename')], - [sg.Input(), sg.FileBrowse()], - [sg.OK(), sg.Cancel()]] - - button, (number,) = sg.Window('Get filename example').Layout(layout).Read() - -you can write this line of code for the exact same result (OK, two lines with the import): - - import PySimpleGUI as sg - - button, (filename,) = sg.Window('Get filename example').Layout( - [[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()]]).Read() - --------------------- -## Multiple Columns - -A Column is required when you have a tall element to the left of smaller elements. - -In this example, there is a Listbox on the left that is 3 rows high. To the right of it are 3 single rows of text and input. These 3 rows are in a Column Element. - -To make it easier to see the Column in the window, the Column background has been shaded blue. The code is wordier than normal due to the blue shading. Each element in the column needs to have the color set to match blue background. - -![cookbook columns](https://user-images.githubusercontent.com/13696193/45309948-f6c52280-b4f2-11e8-8691-a45fa0e06c50.jpg) - - - - import PySimpleGUI as sg - - # Demo of how columns work - # GUI has on row 1 a vertical slider followed by a COLUMN with 7 rows - # Prior to the Column element, this layout was not possible - # Columns layouts look identical to GUI layouts, they are a list of lists of elements. - - sg.ChangeLookAndFeel('BlueMono') - - # Column layout - col = [[sg.Text('col Row 1', text_color='white', background_color='blue')], - [sg.Text('col Row 2', text_color='white', background_color='blue'), sg.Input('col input 1')], - [sg.Text('col Row 3', text_color='white', background_color='blue'), sg.Input('col input 2')]] - - layout = [[sg.Listbox(values=('Listbox Item 1', 'Listbox Item 2', 'Listbox Item 3'), select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE, size=(20,3)), sg.Column(col, background_color='blue')], - [sg.Input('Last input')], - [sg.OK()]] - - # Display the Window and get values - - button, values = sg.Window('Compact 1-line Window with column').Layout(layout).Read() - - sg.Popup(button, values, line_width=200) - - -## Persistent Window With Text Element Updates - -This simple program keep a window open, taking input values until the user terminates the program using the "X" button. - -![math game](https://user-images.githubusercontent.com/13696193/44537842-c9444080-a6cd-11e8-94bc-6cdf1b765dd8.jpg) - - - - import PySimpleGUI as sg - - layout = [ [sg.Txt('Enter values to calculate')], - [sg.In(size=(8,1), key='numerator')], - [sg.Txt('_' * 10)], - [sg.In(size=(8,1), key='denominator')], - [sg.Txt('', size=(8,1), key='output') ], - [sg.ReadButton('Calculate', bind_return_key=True)]] - - window = sg.Window('Math').Layout(layout) - - while True: - button, values = window.Read() - - if button is not None: - try: - numerator = float(values['numerator']) - denominator = float(values['denominator']) - calc = numerator / denominator - except: - calc = 'Invalid' - - window.FindElement('output').Update(calc) - else: - break - - - -## tkinter Canvas Widget - -The Canvas Element is one of the few tkinter objects that are directly accessible. The tkinter Canvas widget itself can be retrieved from a Canvas Element like this: - - can = sg.Canvas(size=(100,100)) - tkcanvas = can.TKCanvas - tkcanvas.create_oval(50, 50, 100, 100) - -While it's fun to scribble on a Canvas Widget, try Graph Element makes it a downright pleasant experience. You do not have to worry about the tkinter coordinate system and can instead work in your own coordinate system. - - -![canvas](https://user-images.githubusercontent.com/13696193/44632429-5266ac00-a948-11e8-9ee0-664103c40178.jpg) - - - import PySimpleGUI as sg - - layout = [ - [sg.Canvas(size=(100, 100), background_color='red', key= 'canvas')], - [sg.T('Change circle color to:'), sg.ReadButton('Red'), sg.ReadButton('Blue')] - ] - - window = sg.Window('Canvas test') - window.Layout(layout) - window.Finalize() - - canvas = window.FindElement('canvas') - cir = canvas.TKCanvas.create_oval(50, 50, 100, 100) - - while True: - button, values = window.Read() - if button is None: - break - if button == 'Blue': - canvas.TKCanvas.itemconfig(cir, fill="Blue") - elif button == 'Red': - canvas.TKCanvas.itemconfig(cir, fill="Red") - -## Graph Element - drawing circle, rectangle, etc, objects - -Just like you can draw on a tkinter widget, you can also draw on a Graph Element. Graph Elements are easier on the programmer as you get to work in your own coordinate system. - -![graph recipe](https://user-images.githubusercontent.com/13696193/45920640-751bb000-be75-11e8-9530-45b71cbae07d.jpg) - - - import PySimpleGUI as sg - - layout = [ - [sg.Graph(canvas_size=(400, 400), graph_bottom_left=(0,0), graph_top_right=(400, 400), background_color='red', key='graph')], - [sg.T('Change circle color to:'), sg.ReadButton('Red'), sg.ReadButton('Blue'), sg.ReadButton('Move')] - ] - - window = sg.Window('Graph test') - window.Layout(layout) - window.Finalize() - - graph = window.FindElement('graph') - circle = graph.DrawCircle((75,75), 25, fill_color='black',line_color='white') - point = graph.DrawPoint((75,75), 10, color='green') - oval = graph.DrawOval((25,300), (100,280), fill_color='purple', line_color='purple' ) - rectangle = graph.DrawRectangle((25,300), (100,280), line_color='purple' ) - line = graph.DrawLine((0,0), (100,100)) - - while True: - button, values = window.Read() - if button is None: - break - if button is 'Blue': - graph.TKCanvas.itemconfig(circle, fill = "Blue") - elif button is 'Red': - graph.TKCanvas.itemconfig(circle, fill = "Red") - elif button is 'Move': - graph.MoveFigure(point, 10,10) - graph.MoveFigure(circle, 10,10) - graph.MoveFigure(oval, 10,10) - graph.MoveFigure(rectangle, 10,10) - - -## Keypad Touchscreen Entry - Input Element Update - -This Recipe implements a Raspberry Pi touchscreen based keypad entry. As the digits are entered using the buttons, the Input Element above it is updated with the input digits. -There are a number of features used in this Recipe including: -* Default Element Size -* auto_size_buttons -* ReadButton -* Dictionary Return values -* Update of Elements in window (Input, Text) -* do_not_clear of Input Elements - - -![keypad 2](https://user-images.githubusercontent.com/13696193/44640891-57504d80-a992-11e8-93f4-4e97e586505e.jpg) - - - - import PySimpleGUI as sg - - # Demonstrates a number of PySimpleGUI features including: - # Default element size - # auto_size_buttons - # ReadButton - # Dictionary return values - # Update of elements in window (Text, Input) - # do_not_clear of Input elements - - layout = [[sg.Text('Enter Your Passcode')], - [sg.Input(size=(10, 1), do_not_clear=True, justification='right', key='input')], - [sg.ReadButton('1'), sg.ReadButton('2'), sg.ReadButton('3')], - [sg.ReadButton('4'), sg.ReadButton('5'), sg.ReadButton('6')], - [sg.ReadButton('7'), sg.ReadButton('8'), sg.ReadButton('9')], - [sg.ReadButton('Submit'), sg.ReadButton('0'), sg.ReadButton('Clear')], - [sg.Text('', size=(15, 1), font=('Helvetica', 18), text_color='red', key='out')], - ] - - window = sg.Window('Keypad', default_button_element_size=(5, 2), auto_size_buttons=False, grab_anywhere=False).Layout(layout) - - # Loop forever reading the window's values, updating the Input field - keys_entered = '' - while True: - button, values = window.Read() # read the window - if button is None: # if the X button clicked, just exit - break - if button == 'Clear': # clear keys if clear button - keys_entered = '' - elif button in '1234567890': - keys_entered = values['input'] # get what's been entered so far - keys_entered += button # add the new digit - elif button == 'Submit': - keys_entered = values['input'] - window.FindElement('out').Update(keys_entered) # output the final string - - window.FindElement('input').Update(keys_entered) # change the window to reflect current key string - -## Animated Matplotlib Graph - -Use the Canvas Element to create an animated graph. The code is a bit tricky to follow, but if you know Matplotlib then this recipe shouldn't be too difficult to copy and modify. - -![animated matplotlib](https://user-images.githubusercontent.com/13696193/44640937-91b9ea80-a992-11e8-9c1c-85ae74013679.jpg) - - - from tkinter import * - from random import randint - import PySimpleGUI as g - from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg - from matplotlib.figure import Figure - import matplotlib.backends.tkagg as tkagg - import tkinter as Tk - - - fig = Figure() - - ax = fig.add_subplot(111) - ax.set_xlabel("X axis") - ax.set_ylabel("Y axis") - ax.grid() - - layout = [[g.Text('Animated Matplotlib', size=(40, 1), justification='center', font='Helvetica 20')], - [g.Canvas(size=(640, 480), key='canvas')], - [g.ReadButton('Exit', size=(10, 2), pad=((280, 0), 3), font='Helvetica 14')]] - - # create the window and show it without the plot - - - window = g.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout) - window.Finalize() # needed to access the canvas element prior to reading the window - - canvas_elem = window.FindElement('canvas') - - graph = FigureCanvasTkAgg(fig, master=canvas_elem.TKCanvas) - canvas = canvas_elem.TKCanvas - - dpts = [randint(0, 10) for x in range(10000)] - # Our event loop - for i in range(len(dpts)): - button, values = window.ReadNonBlocking() - if button == 'Exit' or values is None: - exit(69) - - ax.cla() - ax.grid() - - ax.plot(range(20), dpts[i:i + 20], color='purple') - graph.draw() - figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds - figure_w, figure_h = int(figure_w), int(figure_h) - photo = Tk.PhotoImage(master=canvas, width=figure_w, height=figure_h) - - canvas.create_image(640 / 2, 480 / 2, image=photo) - - figure_canvas_agg = FigureCanvasAgg(fig) - figure_canvas_agg.draw() - - tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2) - - - -## Tight Layout with Button States - -Saw this example layout written in tkinter and liked it so much I duplicated the interface. It's "tight", clean, and has a nice dark look and feel. - -This Recipe also contains code that implements the button interactions so that you'll have a template to build from. - -In other GUI frameworks this program would be most likely "event driven" with callback functions being used to communicate button events. The "event loop" would be handled by the GUI engine. If code already existed that used a call-back mechanism, the loop in the example code below could simply call these callback functions directly based on the button text it receives in the window.Read call. - -![timemanagement](https://user-images.githubusercontent.com/13696193/44996818-0f27c100-af78-11e8-8836-9ef6164efe3b.jpg) - - - import PySimpleGUI as sg - """ - Demonstrates using a "tight" layout with a Dark theme. - Shows how button states can be controlled by a user application. The program manages the disabled/enabled - states for buttons and changes the text color to show greyed-out (disabled) buttons - """ - - sg.ChangeLookAndFeel('Dark') - sg.SetOptions(element_padding=(0,0)) - - layout = [[sg.T('User:', pad=((3,0),0)), sg.OptionMenu(values = ('User 1', 'User 2'), size=(20,1)), sg.T('0', size=(8,1))], - [sg.T('Customer:', pad=((3,0),0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20,1)), sg.T('1', size=(8,1))], - [sg.T('Notes:', pad=((3,0),0)), sg.In(size=(44,1), background_color='white', text_color='black')], - [sg.ReadButton('Start', button_color=('white', 'black'), key='Start'), - sg.ReadButton('Stop', button_color=('white', 'black'), key='Stop'), - sg.ReadButton('Reset', button_color=('white', 'firebrick3'), key='Reset'), - sg.ReadButton('Submit', button_color=('white', 'springgreen4'), key='Submit')] - ] - - window = sg.Window("Time Tracker", default_element_size=(12,1), text_justification='r', auto_size_text=False, auto_size_buttons=False, - default_button_element_size=(12,1)) - window.Layout(layout) - window.Finalize() - window.FindElement('Stop').Update(disabled=True) - window.FindElement('Reset').Update(disabled=True) - window.FindElement('Submit').Update(disabled=True) - recording = have_data = False - while True: - button, values = window.Read() - print(button) - if button is None: - exit(69) - if button is 'Start': - window.FindElement('Start').Update(disabled=True) - window.FindElement('Stop').Update(disabled=False) - window.FindElement('Reset').Update(disabled=False) - window.FindElement('Submit').Update(disabled=True) - recording = True - elif button is 'Stop' and recording: - window.FindElement('Stop').Update(disabled=True) - window.FindElement('Start').Update(disabled=False) - window.FindElement('Submit').Update(disabled=False) - recording = False - have_data = True - elif button is 'Reset': - window.FindElement('Stop').Update(disabled=True) - window.FindElement('Start').Update(disabled=False) - window.FindElement('Submit').Update(disabled=True) - window.FindElement('Reset').Update(disabled=False) - recording = False - have_data = False - elif button is 'Submit' and have_data: - window.FindElement('Stop').Update(disabled=True) - window.FindElement('Start').Update(disabled=False) - window.FindElement('Submit').Update(disabled=True) - window.FindElement('Reset').Update(disabled=False) - recording = False - -## Password Protection For Scripts - -You get 2 scripts in one. - -Use the upper half to generate your hash code. Then paste it into the code in the lower half. Copy and paste lower 1/2 into your code to get password protection for your script without putting the password into your source code. - -![password entry](https://user-images.githubusercontent.com/13696193/45129440-ab58f000-b151-11e8-8ebe-e519a50b3ead.jpg) - -![password hash](https://user-images.githubusercontent.com/13696193/45129441-ab58f000-b151-11e8-8a46-c2789bb7824e.jpg) - - import PySimpleGUI as sg - import hashlib - - ''' - Create a secure login for your scripts without having to include your password in the program. Create an SHA1 hash code for your password using the GUI. Paste into variable in final program - 1. Choose a password - 2. Generate a hash code for your chosen password by running program and entering 'gui' as the password - 3. Type password into the GUI - 4. Copy and paste hash code Window GUI into variable named login_password_hash - 5. Run program again and test your login! - ''' - - # Use this GUI to get your password's hash code - def HashGeneratorGUI(): - layout = [[sg.T('Password Hash Generator', size=(30,1), font='Any 15')], - [sg.T('Password'), sg.In(key='password')], - [sg.T('SHA Hash'), sg.In('', size=(40,1), key='hash')], - ] - - window = sg.Window('SHA Generator', auto_size_text=False, default_element_size=(10,1), - text_justification='r', return_keyboard_events=True, grab_anywhere=False).Layout(layout) - - - while True: - button, values = window.Read() - if button is None: - exit(69) - - password = values['password'] - try: - password_utf = password.encode('utf-8') - sha1hash = hashlib.sha1() - sha1hash.update(password_utf) - password_hash = sha1hash.hexdigest() - window.FindElement('hash').Update(password_hash) - except: - pass - - # ----------------------------- Paste this code into your program / script ----------------------------- - # determine if a password matches the secret password by comparing SHA1 hash codes - def PasswordMatches(password, hash): - password_utf = password.encode('utf-8') - sha1hash = hashlib.sha1() - sha1hash.update(password_utf) - password_hash = sha1hash.hexdigest() - if password_hash == hash: - return True - else: - return False - - login_password_hash = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' - password = sg.PopupGetText('Password', password_char='*') - if password == 'gui': # Remove when pasting into your program - HashGeneratorGUI() # Remove when pasting into your program - exit(69) # Remove when pasting into your program - if PasswordMatches(password, login_password_hash): - print('Login SUCCESSFUL') - else: - print('Login FAILED!!') - - -## Desktop Floating Toolbar - -#### Hiding your windows commmand window -For this and the Time & CPU Widgets you may wish to consider using a tool or technique that will hide your Windows Command Prompt window. I recommend the techniques found on this site: - -[http://www.robvanderwoude.com/battech_hideconsole.php](http://www.robvanderwoude.com/battech_hideconsole.php) - -At the moment I'm using the technique that involves wscript and a script named RunNHide.vbs. They are working beautifully. I'm using a hotkey program and launch by using this script with the command "python.exe insert_program_here.py". I guess the next widget should be one that shows all the programs launched this way so you can kill any bad ones. If you don't properly catch the exit button on your window then your while loop is going to keep on working while your window is no longer there so be careful in your code to always have exit explicitly handled. - - -### Floating toolbar - -This is a cool one! (Sorry about the code pastes... I'm working in it) - -Impress your friends at what a tool-wizard you are by popping a custom toolbar that you keep in the corner of your screen. It stays on top of all your other windows. - - - - -![toolbar gray](https://user-images.githubusercontent.com/13696193/45324308-bfb73700-b51b-11e8-90e7-ab24f3d6e61d.jpg) - -You can easily change colors to match your background by changing a couple of parameters in the code. - -![toolbar black](https://user-images.githubusercontent.com/13696193/45324307-bfb73700-b51b-11e8-8709-6c3c23f737c4.jpg) - - - import PySimpleGUI as sg - import subprocess - import os - import sys - - """ - Demo_Toolbar - A floating toolbar with quick launcher One cool PySimpleGUI demo. Shows borderless windows, grab_anywhere, tight button layout - You can setup a specific program to launch when a button is clicked, or use the Combobox to select a .py file found in the root folder, and run that file. """ - - ROOT_PATH = './' - - def Launcher(): - - def print(line): - window.FindElement('output').Update(line) - - sg.ChangeLookAndFeel('Dark') - - namesonly = [f for f in os.listdir(ROOT_PATH) if f.endswith('.py') ] - - sg.SetOptions(element_padding=(0,0), button_element_size=(12,1), auto_size_buttons=False) - layout = [[sg.Combo(values=namesonly, size=(35,30), key='demofile'), - sg.ReadButton('Run', button_color=('white', '#00168B')), - sg.ReadButton('Program 1'), - sg.ReadButton('Program 2'), - sg.ReadButton('Program 3', button_color=('white', '#35008B')), - sg.Button('EXIT', button_color=('white','firebrick3'))], - [sg.T('', text_color='white', size=(50,1), key='output')]] - - window = sg.Window('Floating Toolbar', no_titlebar=True, keep_on_top=True).Layout(layout) - - # ---===--- Loop taking in user input (buttons) --- # - while True: - (button, value) = window.Read() - if button == 'EXIT' or button is None: - break # exit button clicked - if button == 'Program 1': - print('Run your program 1 here!') - elif button == 'Program 2': - print('Run your program 2 here!') - elif button == 'Run': - file = value['demofile'] - print('Launching %s'%file) - ExecuteCommandSubprocess('python', os.path.join(ROOT_PATH, file)) - else: - print(button) - - def ExecuteCommandSubprocess(command, *args, wait=False): - try: - if sys.platwindow == 'linux': - arg_string = '' - for arg in args: - arg_string += ' ' + str(arg) - sp = subprocess.Popen(['python3' + arg_string, ], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - else: - sp = subprocess.Popen([command, list(args)], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - if wait: - out, err = sp.communicate() - if out: - print(out.decode("utf-8")) - if err: - print(err.decode("utf-8")) - except: pass - - - - if __name__ == '__main__': - Launcher() - - - - -## Desktop Floating Widget - Timer - -This is a little widget you can leave running on your desktop. Will hopefully see more of these for things like checking email, checking server pings, displaying system information, dashboards, etc -. -Much of the code is handling the button states in a fancy way. It could be much simpler if you don't change the button text based on state. - -![timer](https://user-images.githubusercontent.com/13696193/45336349-26a31300-b551-11e8-8b06-d1232ff8ca10.jpg) - - import PySimpleGUI as sg - import time - - """ - Timer Desktop Widget Creates a floating timer that is always on top of other windows You move it by grabbing anywhere on the window Good example of how to do a non-blocking, polling program using PySimpleGUI Can be used to poll hardware when running on a Pi NOTE - you will get a warning message printed when you exit using exit button. It will look something like: invalid command name \"1616802625480StopMove\" - """ - - - # ---------------- Create window ---------------- - sg.ChangeLookAndFeel('Black') - sg.SetOptions(element_padding=(0, 0)) - - layout = [[sg.Text('')], - [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')], - [sg.ReadButton('Pause', key='button', button_color=('white', '#001480')), - sg.ReadButton('Reset', button_color=('white', '#007339'), key='Reset'), - sg.Exit(button_color=('white', 'firebrick4'), key='Exit')]] - - window = sg.Window('Running Timer', no_titlebar=True, auto_size_buttons=False, keep_on_top=True, grab_anywhere=True).Layout(layout) - - - # ---------------- main loop ---------------- - current_time = 0 - paused = False - start_time = int(round(time.time() * 100)) - while (True): - # --------- Read and update window -------- - if not paused: - button, values = window.ReadNonBlocking() - current_time = int(round(time.time() * 100)) - start_time - else: - button, values = window.Read() - if button == 'button': - button = window.FindElement(button).GetText() - # --------- Do Button Operations -------- - if values is None or button == 'Exit': - break - if button is 'Reset': - start_time = int(round(time.time() * 100)) - current_time = 0 - paused_time = start_time - elif button == 'Pause': - paused = True - paused_time = int(round(time.time() * 100)) - element = window.FindElement('button') - element.Update(text='Run') - elif button == 'Run': - paused = False - start_time = start_time + int(round(time.time() * 100)) - paused_time - element = window.FindElement('button') - element.Update(text='Pause') - - # --------- Display timer in window -------- - window.FindElement('text').Update('{:02d}:{:02d}.{:02d}'.format((current_time // 100) // 60, - (current_time // 100) % 60, - current_time % 100)) - time.sleep(.01) - - # --------- After loop -------- - - # Broke out of main loop. Close the window. - window.CloseNonBlocking() - -## Desktop Floating Widget - CPU Utilization - -Like the Timer widget above, this script can be kept running. You will need the package psutil installed in order to run this Recipe. -The spinner changes the number of seconds between reads. Note that you will get an error message printed when exiting because the window does not have have a titlebar. It's a known problem. - - -![cpu widget 2](https://user-images.githubusercontent.com/13696193/45456096-77348080-b6b7-11e8-8906-6663c31ad0eb.jpg) - - - - import PySimpleGUI as sg - import psutil - - # ---------------- Create Window ---------------- - sg.ChangeLookAndFeel('Black') - layout = [[sg.Text('')], - [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')], - [sg.Exit(button_color=('white', 'firebrick4'), pad=((15,0), 0)), sg.Spin([x+1 for x in range(10)], 1, key='spin')]] - - window = sg.Window('Running Timer', no_titlebar=True, auto_size_buttons=False, keep_on_top=True, grab_anywhere=True).Layout(layout) - - - # ---------------- main loop ---------------- - while (True): - # --------- Read and update window -------- - button, values = window.ReadNonBlocking() - - # --------- Do Button Operations -------- - if values is None or button == 'Exit': - break - try: - interval = int(values['spin']) - except: - interval = 1 - - cpu_percent = psutil.cpu_percent(interval=interval) - - # --------- Display timer in window -------- - - window.FindElement('text').Update(f'CPU {cpu_percent:02.0f}%') - - # Broke out of main loop. Close the window. - window.CloseNonBlocking() - -## Menus - -Menus are nothing more than buttons that live in a menu-bar. When you click on a menu item, you get back a "button" with that menu item's text, just as you would had that text been on a button. - -Menu's are defined separately from the GUI window. To add one to your window, simply insert sg.Menu(menu_layout). The menu definition is a list of menu choices and submenus. They are a list of lists. Copy the Recipe and play with it. You'll eventually get when you're looking for. - -If you double click the dashed line at the top of the list of choices, that menu will tear off and become a floating toolbar. How cool! - - -![tear off](https://user-images.githubusercontent.com/13696193/45307668-9aabcf80-b4ed-11e8-9b2b-8564d4bf82a8.jpg) - - - - import PySimpleGUI as sg - - sg.ChangeLookAndFeel('LightGreen') - sg.SetOptions(element_padding=(0, 0)) - - # ------ Menu Definition ------ # - menu_def = [['File', ['Open', 'Save', 'Exit' ]], - ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], - ['Help', 'About...'], ] - - # ------ GUI Defintion ------ # - layout = [ - [sg.Menu(menu_def)], - [sg.Output(size=(60, 20))] - ] - - window = sg.Window("Windows-like program", default_element_size=(12, 1), auto_size_text=False, auto_size_buttons=False, - default_button_element_size=(12, 1)).Layout(layout) - - # ------ Loop & Process button menu choices ------ # - while True: - button, values = window.Read() - if button == None or button == 'Exit': - break - print('Button = ', button) - # ------ Process menu choices ------ # - if button == 'About...': - sg.Popup('About this program', 'Version 1.0', 'PySimpleGUI rocks...') - elif button == 'Open': - filename = sg.PopupGetFile('file to open', no_window=True) - print(filename) - -## Graphing with Graph Element - -Use the Graph Element to draw points, lines, circles, rectangles using ***your*** coordinate systems rather than the underlying graphics coordinates. - -In this example we're defining our graph to be from -100, -100 to +100,+100. That means that zero is in the middle of the drawing. You define this graph description in your call to Graph. - - -![graph markers](https://user-images.githubusercontent.com/13696193/46113087-01eaa480-c1bb-11e8-9784-0dbb4ce728b0.jpg) - - -``` -import math -import PySimpleGUI as sg - -layout = [[sg.Graph(canvas_size=(400, 400), graph_bottom_left=(-105,-105), graph_top_right=(105,105), background_color='white', key='graph', tooltip='This is a cool graph!')],] - -window = sg.Window('Graph of Sine Function', grab_anywhere=True).Layout(layout).Finalize() -graph = window.FindElement('graph') - -# Draw axis -graph.DrawLine((-100,0), (100,0)) -graph.DrawLine((0,-100), (0,100)) - -for x in range(-100, 101, 20): - graph.DrawLine((x,-3), (x,3)) - if x != 0: - graph.DrawText( x, (x,-10), color='green') - -for y in range(-100, 101, 20): - graph.DrawLine((-3,y), (3,y)) - if y != 0: - graph.DrawText( y, (-10,y), color='blue') - -# Draw Graph -for x in range(-100,100): - y = math.sin(x/20)*50 - graph.DrawCircle((x,y), 1, line_color='red', fill_color='red') - -button, values = window.Read() -``` - - - - - -## Tabs - -Tabs bring not only an extra level of sophistication to your window layout, they give you extra room to add more elements. Tabs are one of the 3 container Elements, Elements that hold or contain other Elements. The other two are the Column and Frame Elements. - - -![tabs](https://user-images.githubusercontent.com/13696193/46049479-97732f00-c0fc-11e8-8015-5bbed8bd88bb.jpg) - - - -``` -import PySimpleGUI as sg - -tab1_layout = [[sg.T('This is inside tab 1')]] - -tab2_layout = [[sg.T('This is inside tab 2')], - [sg.In(key='in')]] - -layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, tooltip='tip'), sg.Tab('Tab 2', tab2_layout)]], tooltip='TIP2')], - [sg.RButton('Read')]] - -window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout) - -while True: - button, v = window.Read() - print(button,values) - if button is None: # always, always give a way out! - break -``` - - - - -## Creating a Windows .EXE File - -It's possible to create a single .EXE file that can be distributed to Windows users. There is no requirement to install the Python interpreter on the PC you wish to run it on. Everything it needs is in the one EXE file, assuming you're running a somewhat up to date version of Windows. - -Installation of the packages, you'll need to install PySimpleGUI and PyInstaller (you need to install only once) - - pip install PySimpleGUI - pip install PyInstaller - -To create your EXE file from your program that uses PySimpleGUI, `my_program.py`, enter this command in your Windows command prompt: - - pyinstaller -wF my_program.py - -You will be left with a single file, `my_program.exe`, located in a folder named `dist` under the folder where you executed the `pyinstaller` command. - -That's all... Run your `my_program.exe` file on the Windows machine of your choosing. - -> "It's just that easy." -> -(famous last words that screw up just about anything being referenced) - -Your EXE file should run without creating a "shell window". Only the GUI window should show up on your taskbar. +![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) + + +# The PySimpleGUI Cookbook + + +You'll find that starting with a Recipe will give you a big jump-start on creating your custom GUI. Copy and paste one of these Recipes and modify it to match your requirements. Study them to get an idea of what design patterns to follow. + +The Recipes in this Cookbook all assume you're running on a Python3 machine. If you are running Python 2.7 then your code will differ by 2 character. Replace the import statement: + + import PySimpleGUI as sg + +with + + import PySimpleGUI27 as sg + +There is a short section in the Readme with instruction on installing PySimpleGUI + + +## Simple Data Entry - Return Values As List +Same GUI screen except the return values are in a list instead of a dictionary and doesn't have initial values. + +![super simple 2](https://user-images.githubusercontent.com/13696193/43934091-8100e29a-9c1b-11e8-8d0a-9bd2d13e6d8e.jpg) + + import PySimpleGUI as sg + + # Very basic window. Return values as a list + + layout = [ + [sg.Text('Please enter your Name, Address, Phone')], + [sg.Text('Name', size=(15, 1)), sg.InputText()], + [sg.Text('Address', size=(15, 1)), sg.InputText()], + [sg.Text('Phone', size=(15, 1)), sg.InputText()], + [sg.Submit(), sg.Cancel()] + ] + + window = sg.Window('Simple data entry window').Layout(layout) + event, values = window.Read() + + print(event, values[0], values[1], values[2]) + +## Simple data entry - Return Values As Dictionary +A simple GUI with default values. Results returned in a dictionary. + +![super simple 2](https://user-images.githubusercontent.com/13696193/43934091-8100e29a-9c1b-11e8-8d0a-9bd2d13e6d8e.jpg) + + import PySimpleGUI as sg + + # Very basic window. Return values as a dictionary + + layout = [ + [sg.Text('Please enter your Name, Address, Phone')], + [sg.Text('Name', size=(15, 1)), sg.InputText('name', key='name')], + [sg.Text('Address', size=(15, 1)), sg.InputText('address', key='address')], + [sg.Text('Phone', size=(15, 1)), sg.InputText('phone', key='phone')], + [sg.Submit(), sg.Cancel()] + ] + + window = sg.Window('Simple data entry GUI').Layout(layout) + + event, values = window.Read() + + print(event, values['name'], values['address'], values['phone']) + +--------------------- + + + +----------- +## Simple File Browse +Browse for a filename that is populated into the input field. + +![simple file browse](https://user-images.githubusercontent.com/13696193/43934539-d8bd9490-9c1d-11e8-927f-98b523776fcb.jpg) + + import PySimpleGUI as sg + + GUI_rows = [[sg.Text('SHA-1 and SHA-256 Hashes for the file')], + [sg.InputText(), sg.FileBrowse()], + [sg.Submit(), sg.Cancel()]] + + (event, (source_filename,)) = sg.Window('SHA-1 & 256 Hash').Layout(GUI_rows).Read() + + print(event, source_filename) + +-------------------------- +## Add GUI to Front-End of Script +Quickly add a GUI allowing the user to browse for a filename if a filename is not supplied on the command line using this 1-line GUI. It's the best of both worlds. + +![script front-end](https://user-images.githubusercontent.com/13696193/44756573-39e9c380-aaf9-11e8-97b4-6679f9f5bd46.jpg) + + + import PySimpleGUI as sg + import sys + + if len(sys.argv) == 1: + event, (fname,) = sg.Window('My Script').Layout([[sg.Text('Document to open')], + [sg.In(), sg.FileBrowse()], + [sg.Open(), sg.Cancel()]]).Read() + else: + fname = sys.argv[1] + + if not fname: + sg.Popup("Cancel", "No filename supplied") + raise SystemExit("Cancelling: no filename supplied") + print(event, fname) + + + +-------------- + +## Compare 2 Files + +Browse to get 2 file names that can be then compared. + +![compare 2 files](https://user-images.githubusercontent.com/13696193/43934659-60dc5fbe-9c1e-11e8-8d2b-07c0e3b61892.jpg) + + import PySimpleGUI as sg + + gui_rows = [[sg.Text('Enter 2 files to comare')], + [sg.Text('File 1', size=(8, 1)), sg.InputText(), sg.FileBrowse()], + [sg.Text('File 2', size=(8, 1)), sg.InputText(), sg.FileBrowse()], + [sg.Submit(), sg.Cancel()]] + + window = sg.Window('File Compare').Layout(gui_rows) + + event, values = window.Read() + + print(event, values) + +--------------- +## Nearly All Widgets with Green Color Theme +Example of nearly all of the widgets in a single window. Uses a customized color scheme. + +![latest everything bagel](https://user-images.githubusercontent.com/13696193/45920376-22d89000-be71-11e8-8ac4-640f011f84d0.jpg) + + + + #!/usr/bin/env Python3 + import PySimpleGUI as sg + + sg.ChangeLookAndFeel('GreenTan') + + # ------ Menu Definition ------ # + menu_def = [['File', ['Open', 'Save', 'Exit', 'Properties']], + ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], + ['Help', 'About...'], ] + + # ------ Column Definition ------ # + column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]] + + layout = [ + [sg.Menu(menu_def, tearoff=True)], + [sg.Text('All graphic widgets in one window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)], + [sg.Text('Here is some text.... and a place to enter text')], + [sg.InputText('This is my text')], + [sg.Frame(layout=[ + [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)], + [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')], + [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)), + sg.Multiline(default_text='A second multi-line', size=(35, 3))], + [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)), + sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)], + [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))], + [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)), + sg.Frame('Labelled Group',[[ + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25), + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75), + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10), + sg.Column(column1, background_color='#F7F3EC')]])], + [sg.Text('_' * 80)], + [sg.Text('Choose A Folder', size=(35, 1))], + [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'), + sg.InputText('Default Folder'), sg.FolderBrowse()], + [sg.Submit(tooltip='Click to submit this window'), sg.Cancel()] + ] + + + window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout) + + event, values = window.Read() + + sg.Popup('Title', + 'The results of the window.', + 'The button clicked was "{}"'.format(event), + 'The values are', values) + +------------- + + + + +## Non-Blocking Window With Periodic Update +An async Window that has a event read loop. A Text Element is updated periodically with a running timer. Note that `value` is checked for None which indicates the window was closed using X. + +![non-blocking](https://user-images.githubusercontent.com/13696193/43955295-70f6ac48-9c6d-11e8-8ea2-e6729ba9330c.jpg) + + + import PySimpleGUI as sg + import time + + gui_rows = [[sg.Text('Stopwatch', size=(20, 2), justification='center')], + [sg.Text('', size=(10, 2), font=('Helvetica', 20), justification='center', key='output')], + [sg.T(' ' * 5), sg.ReadButton('Start/Stop', focus=True), sg.Quit()]] + + window = sg.Window('Running Timer').Layout(gui_rows) + + timer_running = True + i = 0 + # Event Loop + while True: + i += 1 * (timer_running is True) + event, values = window.ReadNonBlocking() + + if values is None or event == 'Quit': # if user closed the window using X or clicked Quit button + break + elif event == 'Start/Stop': + timer_running = not timer_running + + window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format((i // 100) // 60, (i // 100) % 60, i % 100)) + time.sleep(.01) + +-------- + +## Callback Function Simulation +The architecture of some programs works better with button callbacks instead of handling in-line. While button callbacks are part of the PySimpleGUI implementation, they are not directly exposed to the caller. The way to get the same result as callbacks is to simulate them with a recipe like this one. + +![button callback 2](https://user-images.githubusercontent.com/13696193/43955588-e139ddc6-9c6e-11e8-8c78-c1c226b8d9b1.jpg) + + import PySimpleGUI as sg + + # This design pattern simulates button callbacks + # Note that callbacks are NOT a part of the package's interface to the + # caller intentionally. The underlying implementation actually does use + # tkinter callbacks. They are simply hidden from the user. + + # The callback functions + def button1(): + print('Button 1 callback') + + def button2(): + print('Button 2 callback') + + + # Layout the design of the GUI + layout = [[sg.Text('Please click a button', auto_size_text=True)], + [sg.ReadButton('1'), sg.ReadButton('2'), sg.Quit()]] + + # Show the Window to the user + window = sg.Window('Button callback example').Layout(layout) + + # Event loop. Read buttons, make callbacks + while True: + # Read the Window + event, value = window.Read() + # Take appropriate action based on button + if event == '1': + button1() + elif event == '2': + button2() + elif event =='Quit' or event is None: + break + + # All done! + sg.PopupOK('Done') + +----- +## Realtime Buttons (Good For Raspberry Pi) +This recipe implements a remote control interface for a robot. There are 4 directions, forward, reverse, left, right. When a button is clicked, PySimpleGUI immediately returns button events for as long as the buttons is held down. When released, the button events stop. This is an async/non-blocking window. + +![robot control](https://user-images.githubusercontent.com/13696193/44006710-d227f23e-9e56-11e8-89a3-2be5b2726199.jpg) + + import PySimpleGUI as sg + + + gui_rows = [[sg.Text('Robotics Remote Control')], + [sg.T(' ' * 10), sg.RealtimeButton('Forward')], + [sg.RealtimeButton('Left'), sg.T(' ' * 15), sg.RealtimeButton('Right')], + [sg.T(' ' * 10), sg.RealtimeButton('Reverse')], + [sg.T('')], + [sg.Quit(button_color=('black', 'orange'))] + ] + + window = sg.Window('Robotics Remote Control', auto_size_text=True).Layout(gui_rows) + + # + # Some place later in your code... + # You need to perform a ReadNonBlocking on your window every now and then or + # else it won't refresh. + # + # your program's main loop + while (True): + # This is the code that reads and updates your window + event, values = window.ReadNonBlocking() + if event is not None: + print(event) + if event == 'Quit' or values is None: + break + + window.CloseNonBlocking() # Don't forget to close your window! + +--------- + +## OneLineProgressMeter + +This recipe shows just how easy it is to add a progress meter to your code. + +![onelineprogressmeter](https://user-images.githubusercontent.com/13696193/45589254-bd285900-b8f0-11e8-9122-b43f06bf074d.jpg) + + + import PySimpleGUI as sg + + for i in range(1000): + sg.OneLineProgressMeter('One Line Meter Example', i+1, 1000, 'key') + + + +----- +## Button Graphics (Media Player) +Buttons can have PNG of GIF images on them. This Media Player recipe requires 4 images in order to function correctly. The background is set to the same color as the button background so that they blend together. + +![media player](https://user-images.githubusercontent.com/13696193/43958418-5dd133f2-9c79-11e8-9432-0a67007e85ac.jpg) + + import PySimpleGUI as sg + + background = '#F0F0F0' + # Set the backgrounds the same as the background on the buttons + sg.SetOptions(background_color=background, element_background_color=background) + # Images are located in a subfolder in the Demo Media Player.py folder + image_pause = './ButtonGraphics/Pause.png' + image_restart = './ButtonGraphics/Restart.png' + image_next = './ButtonGraphics/Next.png' + image_exit = './ButtonGraphics/Exit.png' + + # define layout of the rows + layout = [[sg.Text('Media File Player', size=(17, 1), font=("Helvetica", 25))], + [sg.Text('', size=(15, 2), font=("Helvetica", 14), key='output')], + [sg.ReadButton('Restart Song', button_color=(background, background), + image_filename=image_restart, image_size=(50, 50), image_subsample=2, border_width=0), + sg.Text(' ' * 2), + sg.ReadButton('Pause', button_color=(background, background), + image_filename=image_pause, image_size=(50, 50), image_subsample=2, border_width=0), + sg.Text(' ' * 2), + sg.ReadButton('Next', button_color=(background, background), + image_filename=image_next, image_size=(50, 50), image_subsample=2, border_width=0), + sg.Text(' ' * 2), + sg.Text(' ' * 2), sg.Button('Exit', button_color=(background, background), + image_filename=image_exit, image_size=(50, 50), image_subsample=2, + border_width=0)], + [sg.Text('_' * 30)], + [sg.Text(' ' * 30)], + [ + sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', + font=("Helvetica", 15)), + sg.Text(' ' * 2), + sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', + font=("Helvetica", 15)), + sg.Text(' ' * 8), + sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', + font=("Helvetica", 15))], + [sg.Text('Bass', font=("Helvetica", 15), size=(6, 1)), + sg.Text('Treble', font=("Helvetica", 15), size=(10, 1)), + sg.Text('Volume', font=("Helvetica", 15), size=(7, 1))] + ] + + window = sg.Window('Media File Player', auto_size_text=True, default_element_size=(20, 1), + font=("Helvetica", 25)).Layout(layout) + # Our event loop + while (True): + # Read the window (this call will not block) + event, values = window.ReadNonBlocking() + if event == 'Exit' or values is None: + break + # If a button was pressed, display it on the GUI by updating the text element + if event: + window.FindElement('output').Update(event) + +---- +## Script Launcher - Persistent Window +This Window doesn't close after button clicks. To achieve this the buttons are specified as `sg.ReadButton` instead of `sg.Button`. The exception to this is the EXIT button. Clicking it will close the window. This program will run commands and display the output in the scrollable window. + +![launcher 2](https://user-images.githubusercontent.com/13696193/43958519-b30af218-9c79-11e8-88da-fadc69da818c.jpg) + + import PySimpleGUI as sg + import subprocess + + # Please check Demo programs for better examples of launchers + def ExecuteCommandSubprocess(command, *args): + try: + sp = subprocess.Popen([command, *args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = sp.communicate() + if out: + print(out.decode("utf-8")) + if err: + print(err.decode("utf-8")) + except: + pass + + + layout = [ + [sg.Text('Script output....', size=(40, 1))], + [sg.Output(size=(88, 20))], + [sg.ReadButton('script1'), sg.ReadButton('script2'), sg.Button('EXIT')], + [sg.Text('Manual command', size=(15, 1)), sg.InputText(focus=True), sg.ReadButton('Run', bind_return_key=True)] + ] + + + window = sg.Window('Script launcher').Layout(layout) + + # ---===--- Loop taking in user input and using it to call scripts --- # + + while True: + (event, value) = window.Read() + if event == 'EXIT' or event is None: + break # exit button clicked + if event == 'script1': + ExecuteCommandSubprocess('pip', 'list') + elif event == 'script2': + ExecuteCommandSubprocess('python', '--version') + elif event == 'Run': + ExecuteCommandSubprocess(value[0]) + +---- +## Machine Learning GUI +A standard non-blocking GUI with lots of inputs. + +![machine learning green](https://user-images.githubusercontent.com/13696193/43979000-408b77ba-9cb7-11e8-9ffd-24c156767532.jpg) + + import PySimpleGUI as sg + + # Green & tan color scheme + sg.ChangeLookAndFeel('GreenTan') + + sg.SetOptions(text_justification='right') + + layout = [[sg.Text('Machine Learning Command Line Parameters', font=('Helvetica', 16))], + [sg.Text('Passes', size=(15, 1)), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1)), + sg.Text('Steps', size=(18, 1)), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1))], + [sg.Text('ooa', size=(15, 1)), sg.In(default_text='6', size=(10, 1)), sg.Text('nn', size=(15, 1)), + sg.In(default_text='10', size=(10, 1))], + [sg.Text('q', size=(15, 1)), sg.In(default_text='ff', size=(10, 1)), sg.Text('ngram', size=(15, 1)), + sg.In(default_text='5', size=(10, 1))], + [sg.Text('l', size=(15, 1)), sg.In(default_text='0.4', size=(10, 1)), sg.Text('Layers', size=(15, 1)), + sg.Drop(values=('BatchNorm', 'other'), auto_size_text=True)], + [sg.Text('_' * 100, size=(65, 1))], + [sg.Text('Flags', font=('Helvetica', 15), justification='left')], + [sg.Checkbox('Normalize', size=(12, 1), default=True), sg.Checkbox('Verbose', size=(20, 1))], + [sg.Checkbox('Cluster', size=(12, 1)), sg.Checkbox('Flush Output', size=(20, 1), default=True)], + [sg.Checkbox('Write Results', size=(12, 1)), sg.Checkbox('Keep Intermediate Data', size=(20, 1))], + [sg.Text('_' * 100, size=(65, 1))], + [sg.Text('Loss Functions', font=('Helvetica', 15), justification='left')], + [sg.Radio('Cross-Entropy', 'loss', size=(12, 1)), sg.Radio('Logistic', 'loss', default=True, size=(12, 1))], + [sg.Radio('Hinge', 'loss', size=(12, 1)), sg.Radio('Huber', 'loss', size=(12, 1))], + [sg.Radio('Kullerback', 'loss', size=(12, 1)), sg.Radio('MAE(L1)', 'loss', size=(12, 1))], + [sg.Radio('MSE(L2)', 'loss', size=(12, 1)), sg.Radio('MB(L0)', 'loss', size=(12, 1))], + [sg.Submit(), sg.Cancel()]] + + window = sg.Window('Machine Learning Front End', font=("Helvetica", 12)).Layout(layout) + + event, values = window.Read() + +------- +## Custom Progress Meter / Progress Bar +Perhaps you don't want all the statistics that the EasyProgressMeter provides and want to create your own progress bar. Use this recipe to do just that. + +![custom progress meter](https://user-images.githubusercontent.com/13696193/43982958-3393b23e-9cc6-11e8-8b49-e7f4890cbc4b.jpg) + + + import PySimpleGUI as sg + + # layout the Window + layout = [[sg.Text('A custom progress meter')], + [sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progbar')], + [sg.Cancel()]] + + # create the Window + window = sg.Window('Custom Progress Meter').Layout(layout) + # loop that would normally do something useful + for i in range(10000): + # check to see if the cancel button was clicked and exit loop if clicked + event, values = window.ReadNonBlocking() + if event == 'Cancel' or values == None: + break + # update bar with loop value +1 so that bar eventually reaches the maximum + window.FindElement('progbar').UpdateBar(i + 1) + # done with loop... need to destroy the window as it's still open + window.CloseNonBlocking() + + + ---- + +## The One-Line GUI + +For those of you into super-compact code, a complete customized GUI can be specified, shown, and received the results using a single line of Python code. + + +![simple](https://user-images.githubusercontent.com/13696193/44227935-ecb53b80-a161-11e8-968b-b3f963404dec.jpg) + + +Instead of + + import PySimpleGUI as sg + + layout = [[sg.Text('Filename')], + [sg.Input(), sg.FileBrowse()], + [sg.OK(), sg.Cancel()]] + + event, (number,) = sg.Window('Get filename example').Layout(layout).Read() + +you can write this line of code for the exact same result (OK, two lines with the import): + + import PySimpleGUI as sg + + event, (filename,) = sg.Window('Get filename example').Layout( + [[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()]]).Read() + +-------------------- +## Multiple Columns + +A Column is required when you have a tall element to the left of smaller elements. + +In this example, there is a Listbox on the left that is 3 rows high. To the right of it are 3 single rows of text and input. These 3 rows are in a Column Element. + +To make it easier to see the Column in the window, the Column background has been shaded blue. The code is wordier than normal due to the blue shading. Each element in the column needs to have the color set to match blue background. + +![cookbook columns](https://user-images.githubusercontent.com/13696193/45309948-f6c52280-b4f2-11e8-8691-a45fa0e06c50.jpg) + + + + import PySimpleGUI as sg + + # Demo of how columns work + # GUI has on row 1 a vertical slider followed by a COLUMN with 7 rows + # Prior to the Column element, this layout was not possible + # Columns layouts look identical to GUI layouts, they are a list of lists of elements. + + sg.ChangeLookAndFeel('BlueMono') + + # Column layout + col = [[sg.Text('col Row 1', text_color='white', background_color='blue')], + [sg.Text('col Row 2', text_color='white', background_color='blue'), sg.Input('col input 1')], + [sg.Text('col Row 3', text_color='white', background_color='blue'), sg.Input('col input 2')]] + + layout = [[sg.Listbox(values=('Listbox Item 1', 'Listbox Item 2', 'Listbox Item 3'), select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE, size=(20,3)), sg.Column(col, background_color='blue')], + [sg.Input('Last input')], + [sg.OK()]] + + # Display the Window and get values + + event, values = sg.Window('Compact 1-line Window with column').Layout(layout).Read() + + sg.Popup(event, values, line_width=200) + + +## Persistent Window With Text Element Updates + +This simple program keep a window open, taking input values until the user terminates the program using the "X" button. + +![math game](https://user-images.githubusercontent.com/13696193/44537842-c9444080-a6cd-11e8-94bc-6cdf1b765dd8.jpg) + + + + import PySimpleGUI as sg + + layout = [ [sg.Txt('Enter values to calculate')], + [sg.In(size=(8,1), key='numerator')], + [sg.Txt('_' * 10)], + [sg.In(size=(8,1), key='denominator')], + [sg.Txt('', size=(8,1), key='output') ], + [sg.ReadButton('Calculate', bind_return_key=True)]] + + window = sg.Window('Math').Layout(layout) + + while True: + event, values = window.Read() + + if event is not None: + try: + numerator = float(values['numerator']) + denominator = float(values['denominator']) + calc = numerator / denominator + except: + calc = 'Invalid' + + window.FindElement('output').Update(calc) + else: + break + + + +## tkinter Canvas Widget + +The Canvas Element is one of the few tkinter objects that are directly accessible. The tkinter Canvas widget itself can be retrieved from a Canvas Element like this: + + can = sg.Canvas(size=(100,100)) + tkcanvas = can.TKCanvas + tkcanvas.create_oval(50, 50, 100, 100) + +While it's fun to scribble on a Canvas Widget, try Graph Element makes it a downright pleasant experience. You do not have to worry about the tkinter coordinate system and can instead work in your own coordinate system. + + +![canvas](https://user-images.githubusercontent.com/13696193/44632429-5266ac00-a948-11e8-9ee0-664103c40178.jpg) + + + import PySimpleGUI as sg + + layout = [ + [sg.Canvas(size=(100, 100), background_color='red', key= 'canvas')], + [sg.T('Change circle color to:'), sg.ReadButton('Red'), sg.ReadButton('Blue')] + ] + + window = sg.Window('Canvas test') + window.Layout(layout) + window.Finalize() + + canvas = window.FindElement('canvas') + cir = canvas.TKCanvas.create_oval(50, 50, 100, 100) + + while True: + event, values = window.Read() + if event is None: + break + if event == 'Blue': + canvas.TKCanvas.itemconfig(cir, fill="Blue") + elif event == 'Red': + canvas.TKCanvas.itemconfig(cir, fill="Red") + +## Graph Element - drawing circle, rectangle, etc, objects + +Just like you can draw on a tkinter widget, you can also draw on a Graph Element. Graph Elements are easier on the programmer as you get to work in your own coordinate system. + +![graph recipe](https://user-images.githubusercontent.com/13696193/45920640-751bb000-be75-11e8-9530-45b71cbae07d.jpg) + + + import PySimpleGUI as sg + + layout = [ + [sg.Graph(canvas_size=(400, 400), graph_bottom_left=(0,0), graph_top_right=(400, 400), background_color='red', key='graph')], + [sg.T('Change circle color to:'), sg.ReadButton('Red'), sg.ReadButton('Blue'), sg.ReadButton('Move')] + ] + + window = sg.Window('Graph test') + window.Layout(layout) + window.Finalize() + + graph = window.FindElement('graph') + circle = graph.DrawCircle((75,75), 25, fill_color='black',line_color='white') + point = graph.DrawPoint((75,75), 10, color='green') + oval = graph.DrawOval((25,300), (100,280), fill_color='purple', line_color='purple' ) + rectangle = graph.DrawRectangle((25,300), (100,280), line_color='purple' ) + line = graph.DrawLine((0,0), (100,100)) + + while True: + event, values = window.Read() + if event is None: + break + if event is 'Blue': + graph.TKCanvas.itemconfig(circle, fill = "Blue") + elif event is 'Red': + graph.TKCanvas.itemconfig(circle, fill = "Red") + elif event is 'Move': + graph.MoveFigure(point, 10,10) + graph.MoveFigure(circle, 10,10) + graph.MoveFigure(oval, 10,10) + graph.MoveFigure(rectangle, 10,10) + + +## Keypad Touchscreen Entry - Input Element Update + +This Recipe implements a Raspberry Pi touchscreen based keypad entry. As the digits are entered using the buttons, the Input Element above it is updated with the input digits. +There are a number of features used in this Recipe including: +* Default Element Size +* auto_size_buttons +* ReadButton +* Dictionary Return values +* Update of Elements in window (Input, Text) +* do_not_clear of Input Elements + + +![keypad 2](https://user-images.githubusercontent.com/13696193/44640891-57504d80-a992-11e8-93f4-4e97e586505e.jpg) + + + + import PySimpleGUI as sg + + # Demonstrates a number of PySimpleGUI features including: + # Default element size + # auto_size_buttons + # ReadButton + # Dictionary return values + # Update of elements in window (Text, Input) + # do_not_clear of Input elements + + layout = [[sg.Text('Enter Your Passcode')], + [sg.Input(size=(10, 1), do_not_clear=True, justification='right', key='input')], + [sg.ReadButton('1'), sg.ReadButton('2'), sg.ReadButton('3')], + [sg.ReadButton('4'), sg.ReadButton('5'), sg.ReadButton('6')], + [sg.ReadButton('7'), sg.ReadButton('8'), sg.ReadButton('9')], + [sg.ReadButton('Submit'), sg.ReadButton('0'), sg.ReadButton('Clear')], + [sg.Text('', size=(15, 1), font=('Helvetica', 18), text_color='red', key='out')], + ] + + window = sg.Window('Keypad', default_button_element_size=(5, 2), auto_size_buttons=False, grab_anywhere=False).Layout(layout) + + # Loop forever reading the window's values, updating the Input field + keys_entered = '' + while True: + event, values = window.Read() # read the window + if event is None: # if the X button clicked, just exit + break + if event == 'Clear': # clear keys if clear button + keys_entered = '' + elif event in '1234567890': + keys_entered = values['input'] # get what's been entered so far + keys_entered += event # add the new digit + elif event == 'Submit': + keys_entered = values['input'] + window.FindElement('out').Update(keys_entered) # output the final string + + window.FindElement('input').Update(keys_entered) # change the window to reflect current key string + +## Animated Matplotlib Graph + +Use the Canvas Element to create an animated graph. The code is a bit tricky to follow, but if you know Matplotlib then this recipe shouldn't be too difficult to copy and modify. + +![animated matplotlib](https://user-images.githubusercontent.com/13696193/44640937-91b9ea80-a992-11e8-9c1c-85ae74013679.jpg) + + + from tkinter import * + from random import randint + import PySimpleGUI as g + from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg + from matplotlib.figure import Figure + import matplotlib.backends.tkagg as tkagg + import tkinter as Tk + + + fig = Figure() + + ax = fig.add_subplot(111) + ax.set_xlabel("X axis") + ax.set_ylabel("Y axis") + ax.grid() + + layout = [[g.Text('Animated Matplotlib', size=(40, 1), justification='center', font='Helvetica 20')], + [g.Canvas(size=(640, 480), key='canvas')], + [g.ReadButton('Exit', size=(10, 2), pad=((280, 0), 3), font='Helvetica 14')]] + + # create the window and show it without the plot + + + window = g.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout) + window.Finalize() # needed to access the canvas element prior to reading the window + + canvas_elem = window.FindElement('canvas') + + graph = FigureCanvasTkAgg(fig, master=canvas_elem.TKCanvas) + canvas = canvas_elem.TKCanvas + + dpts = [randint(0, 10) for x in range(10000)] + # Our event loop + for i in range(len(dpts)): + event, values = window.ReadNonBlocking() + if event == 'Exit' or values is None: + exit(69) + + ax.cla() + ax.grid() + + ax.plot(range(20), dpts[i:i + 20], color='purple') + graph.draw() + figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds + figure_w, figure_h = int(figure_w), int(figure_h) + photo = Tk.PhotoImage(master=canvas, width=figure_w, height=figure_h) + + canvas.create_image(640 / 2, 480 / 2, image=photo) + + figure_canvas_agg = FigureCanvasAgg(fig) + figure_canvas_agg.draw() + + tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2) + + + +## Tight Layout with Button States + +Saw this example layout written in tkinter and liked it so much I duplicated the interface. It's "tight", clean, and has a nice dark look and feel. + +This Recipe also contains code that implements the button interactions so that you'll have a template to build from. + +In other GUI frameworks this program would be most likely "event driven" with callback functions being used to communicate button events. The "event loop" would be handled by the GUI engine. If code already existed that used a call-back mechanism, the loop in the example code below could simply call these callback functions directly based on the button text it receives in the window.Read call. + +![timemanagement](https://user-images.githubusercontent.com/13696193/44996818-0f27c100-af78-11e8-8836-9ef6164efe3b.jpg) + + + import PySimpleGUI as sg + """ + Demonstrates using a "tight" layout with a Dark theme. + Shows how button states can be controlled by a user application. The program manages the disabled/enabled + states for buttons and changes the text color to show greyed-out (disabled) buttons + """ + + sg.ChangeLookAndFeel('Dark') + sg.SetOptions(element_padding=(0,0)) + + layout = [[sg.T('User:', pad=((3,0),0)), sg.OptionMenu(values = ('User 1', 'User 2'), size=(20,1)), sg.T('0', size=(8,1))], + [sg.T('Customer:', pad=((3,0),0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20,1)), sg.T('1', size=(8,1))], + [sg.T('Notes:', pad=((3,0),0)), sg.In(size=(44,1), background_color='white', text_color='black')], + [sg.ReadButton('Start', button_color=('white', 'black'), key='Start'), + sg.ReadButton('Stop', button_color=('white', 'black'), key='Stop'), + sg.ReadButton('Reset', button_color=('white', 'firebrick3'), key='Reset'), + sg.ReadButton('Submit', button_color=('white', 'springgreen4'), key='Submit')] + ] + + window = sg.Window("Time Tracker", default_element_size=(12,1), text_justification='r', auto_size_text=False, auto_size_buttons=False, + default_button_element_size=(12,1)) + window.Layout(layout) + window.Finalize() + window.FindElement('Stop').Update(disabled=True) + window.FindElement('Reset').Update(disabled=True) + window.FindElement('Submit').Update(disabled=True) + recording = have_data = False + while True: + event, values = window.Read() + print(event) + if event is None: + exit(69) + if event is 'Start': + window.FindElement('Start').Update(disabled=True) + window.FindElement('Stop').Update(disabled=False) + window.FindElement('Reset').Update(disabled=False) + window.FindElement('Submit').Update(disabled=True) + recording = True + elif event is 'Stop' and recording: + window.FindElement('Stop').Update(disabled=True) + window.FindElement('Start').Update(disabled=False) + window.FindElement('Submit').Update(disabled=False) + recording = False + have_data = True + elif event is 'Reset': + window.FindElement('Stop').Update(disabled=True) + window.FindElement('Start').Update(disabled=False) + window.FindElement('Submit').Update(disabled=True) + window.FindElement('Reset').Update(disabled=False) + recording = False + have_data = False + elif event is 'Submit' and have_data: + window.FindElement('Stop').Update(disabled=True) + window.FindElement('Start').Update(disabled=False) + window.FindElement('Submit').Update(disabled=True) + window.FindElement('Reset').Update(disabled=False) + recording = False + +## Password Protection For Scripts + +You get 2 scripts in one. + +Use the upper half to generate your hash code. Then paste it into the code in the lower half. Copy and paste lower 1/2 into your code to get password protection for your script without putting the password into your source code. + +![password entry](https://user-images.githubusercontent.com/13696193/45129440-ab58f000-b151-11e8-8ebe-e519a50b3ead.jpg) + +![password hash](https://user-images.githubusercontent.com/13696193/45129441-ab58f000-b151-11e8-8a46-c2789bb7824e.jpg) + + import PySimpleGUI as sg + import hashlib + + ''' + Create a secure login for your scripts without having to include your password in the program. Create an SHA1 hash code for your password using the GUI. Paste into variable in final program + 1. Choose a password + 2. Generate a hash code for your chosen password by running program and entering 'gui' as the password + 3. Type password into the GUI + 4. Copy and paste hash code Window GUI into variable named login_password_hash + 5. Run program again and test your login! + ''' + + # Use this GUI to get your password's hash code + def HashGeneratorGUI(): + layout = [[sg.T('Password Hash Generator', size=(30,1), font='Any 15')], + [sg.T('Password'), sg.In(key='password')], + [sg.T('SHA Hash'), sg.In('', size=(40,1), key='hash')], + ] + + window = sg.Window('SHA Generator', auto_size_text=False, default_element_size=(10,1), + text_justification='r', return_keyboard_events=True, grab_anywhere=False).Layout(layout) + + + while True: + event, values = window.Read() + if event is None: + exit(69) + + password = values['password'] + try: + password_utf = password.encode('utf-8') + sha1hash = hashlib.sha1() + sha1hash.update(password_utf) + password_hash = sha1hash.hexdigest() + window.FindElement('hash').Update(password_hash) + except: + pass + + # ----------------------------- Paste this code into your program / script ----------------------------- + # determine if a password matches the secret password by comparing SHA1 hash codes + def PasswordMatches(password, hash): + password_utf = password.encode('utf-8') + sha1hash = hashlib.sha1() + sha1hash.update(password_utf) + password_hash = sha1hash.hexdigest() + if password_hash == hash: + return True + else: + return False + + login_password_hash = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' + password = sg.PopupGetText('Password', password_char='*') + if password == 'gui': # Remove when pasting into your program + HashGeneratorGUI() # Remove when pasting into your program + exit(69) # Remove when pasting into your program + if PasswordMatches(password, login_password_hash): + print('Login SUCCESSFUL') + else: + print('Login FAILED!!') + + +## Desktop Floating Toolbar + +#### Hiding your windows commmand window +For this and the Time & CPU Widgets you may wish to consider using a tool or technique that will hide your Windows Command Prompt window. I recommend the techniques found on this site: + +[http://www.robvanderwoude.com/battech_hideconsole.php](http://www.robvanderwoude.com/battech_hideconsole.php) + +At the moment I'm using the technique that involves wscript and a script named RunNHide.vbs. They are working beautifully. I'm using a hotkey program and launch by using this script with the command "python.exe insert_program_here.py". I guess the next widget should be one that shows all the programs launched this way so you can kill any bad ones. If you don't properly catch the exit button on your window then your while loop is going to keep on working while your window is no longer there so be careful in your code to always have exit explicitly handled. + + +### Floating toolbar + +This is a cool one! (Sorry about the code pastes... I'm working in it) + +Impress your friends at what a tool-wizard you are by popping a custom toolbar that you keep in the corner of your screen. It stays on top of all your other windows. + + + + +![toolbar gray](https://user-images.githubusercontent.com/13696193/45324308-bfb73700-b51b-11e8-90e7-ab24f3d6e61d.jpg) + +You can easily change colors to match your background by changing a couple of parameters in the code. + +![toolbar black](https://user-images.githubusercontent.com/13696193/45324307-bfb73700-b51b-11e8-8709-6c3c23f737c4.jpg) + + + import PySimpleGUI as sg + import subprocess + import os + import sys + + """ + Demo_Toolbar - A floating toolbar with quick launcher One cool PySimpleGUI demo. Shows borderless windows, grab_anywhere, tight button layout + You can setup a specific program to launch when a button is clicked, or use the Combobox to select a .py file found in the root folder, and run that file. """ + + ROOT_PATH = './' + + def Launcher(): + + def print(line): + window.FindElement('output').Update(line) + + sg.ChangeLookAndFeel('Dark') + + namesonly = [f for f in os.listdir(ROOT_PATH) if f.endswith('.py') ] + + sg.SetOptions(element_padding=(0,0), button_element_size=(12,1), auto_size_buttons=False) + layout = [[sg.Combo(values=namesonly, size=(35,30), key='demofile'), + sg.ReadButton('Run', button_color=('white', '#00168B')), + sg.ReadButton('Program 1'), + sg.ReadButton('Program 2'), + sg.ReadButton('Program 3', button_color=('white', '#35008B')), + sg.Button('EXIT', button_color=('white','firebrick3'))], + [sg.T('', text_color='white', size=(50,1), key='output')]] + + window = sg.Window('Floating Toolbar', no_titlebar=True, keep_on_top=True).Layout(layout) + + # ---===--- Loop taking in user input (events) --- # + while True: + (event, value) = window.Read() + if event == 'EXIT' or event is None: + break # exit button clicked + if event == 'Program 1': + print('Run your program 1 here!') + elif event == 'Program 2': + print('Run your program 2 here!') + elif event == 'Run': + file = value['demofile'] + print('Launching %s'%file) + ExecuteCommandSubprocess('python', os.path.join(ROOT_PATH, file)) + else: + print(event) + + def ExecuteCommandSubprocess(command, *args, wait=False): + try: + if sys.platwindow == 'linux': + arg_string = '' + for arg in args: + arg_string += ' ' + str(arg) + sp = subprocess.Popen(['python3' + arg_string, ], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + sp = subprocess.Popen([command, list(args)], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if wait: + out, err = sp.communicate() + if out: + print(out.decode("utf-8")) + if err: + print(err.decode("utf-8")) + except: pass + + + + if __name__ == '__main__': + Launcher() + + + + +## Desktop Floating Widget - Timer + +This is a little widget you can leave running on your desktop. Will hopefully see more of these for things like checking email, checking server pings, displaying system information, dashboards, etc +. +Much of the code is handling the button states in a fancy way. It could be much simpler if you don't change the button text based on state. + +![timer](https://user-images.githubusercontent.com/13696193/45336349-26a31300-b551-11e8-8b06-d1232ff8ca10.jpg) + + import PySimpleGUI as sg + import time + + """ + Timer Desktop Widget Creates a floating timer that is always on top of other windows You move it by grabbing anywhere on the window Good example of how to do a non-blocking, polling program using PySimpleGUI Can be used to poll hardware when running on a Pi NOTE - you will get a warning message printed when you exit using exit button. It will look something like: invalid command name \"1616802625480StopMove\" + """ + + + # ---------------- Create window ---------------- + sg.ChangeLookAndFeel('Black') + sg.SetOptions(element_padding=(0, 0)) + + layout = [[sg.Text('')], + [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')], + [sg.ReadButton('Pause', key='button', button_color=('white', '#001480')), + sg.ReadButton('Reset', button_color=('white', '#007339'), key='Reset'), + sg.Exit(button_color=('white', 'firebrick4'), key='Exit')]] + + window = sg.Window('Running Timer', no_titlebar=True, auto_size_buttons=False, keep_on_top=True, grab_anywhere=True).Layout(layout) + + + # ---------------- main loop ---------------- + current_time = 0 + paused = False + start_time = int(round(time.time() * 100)) + while (True): + # --------- Read and update window -------- + if not paused: + event, values = window.ReadNonBlocking() + current_time = int(round(time.time() * 100)) - start_time + else: + event, values = window.Read() + if event == 'button': + button = window.FindElement(button).GetText() + # --------- Do Button Operations -------- + if values is None or event == 'Exit': + break + if event is 'Reset': + start_time = int(round(time.time() * 100)) + current_time = 0 + paused_time = start_time + elif event == 'Pause': + paused = True + paused_time = int(round(time.time() * 100)) + element = window.FindElement('button') + element.Update(text='Run') + elif event == 'Run': + paused = False + start_time = start_time + int(round(time.time() * 100)) - paused_time + element = window.FindElement('button') + element.Update(text='Pause') + + # --------- Display timer in window -------- + window.FindElement('text').Update('{:02d}:{:02d}.{:02d}'.format((current_time // 100) // 60, + (current_time // 100) % 60, + current_time % 100)) + time.sleep(.01) + + # --------- After loop -------- + + # Broke out of main loop. Close the window. + window.CloseNonBlocking() + +## Desktop Floating Widget - CPU Utilization + +Like the Timer widget above, this script can be kept running. You will need the package psutil installed in order to run this Recipe. +The spinner changes the number of seconds between reads. Note that you will get an error message printed when exiting because the window does not have have a titlebar. It's a known problem. + + +![cpu widget 2](https://user-images.githubusercontent.com/13696193/45456096-77348080-b6b7-11e8-8906-6663c31ad0eb.jpg) + + + + import PySimpleGUI as sg + import psutil + + # ---------------- Create Window ---------------- + sg.ChangeLookAndFeel('Black') + layout = [[sg.Text('')], + [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')], + [sg.Exit(button_color=('white', 'firebrick4'), pad=((15,0), 0)), sg.Spin([x+1 for x in range(10)], 1, key='spin')]] + + window = sg.Window('Running Timer', no_titlebar=True, auto_size_buttons=False, keep_on_top=True, grab_anywhere=True).Layout(layout) + + + # ---------------- main loop ---------------- + while (True): + # --------- Read and update window -------- + event, values = window.ReadNonBlocking() + + # --------- Do Button Operations -------- + if values is None or event == 'Exit': + break + try: + interval = int(values['spin']) + except: + interval = 1 + + cpu_percent = psutil.cpu_percent(interval=interval) + + # --------- Display timer in window -------- + + window.FindElement('text').Update(f'CPU {cpu_percent:02.0f}%') + + # Broke out of main loop. Close the window. + window.CloseNonBlocking() + +## Menus + +Menus are nothing more than buttons that live in a menu-bar. When you click on a menu item, you get back a "button" with that menu item's text, just as you would had that text been on a button. + +Menu's are defined separately from the GUI window. To add one to your window, simply insert sg.Menu(menu_layout). The menu definition is a list of menu choices and submenus. They are a list of lists. Copy the Recipe and play with it. You'll eventually get when you're looking for. + +If you double click the dashed line at the top of the list of choices, that menu will tear off and become a floating toolbar. How cool! + + +![tear off](https://user-images.githubusercontent.com/13696193/45307668-9aabcf80-b4ed-11e8-9b2b-8564d4bf82a8.jpg) + + + + import PySimpleGUI as sg + + sg.ChangeLookAndFeel('LightGreen') + sg.SetOptions(element_padding=(0, 0)) + + # ------ Menu Definition ------ # + menu_def = [['File', ['Open', 'Save', 'Exit' ]], + ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], + ['Help', 'About...'], ] + + # ------ GUI Defintion ------ # + layout = [ + [sg.Menu(menu_def)], + [sg.Output(size=(60, 20))] + ] + + window = sg.Window("Windows-like program", default_element_size=(12, 1), auto_size_text=False, auto_size_buttons=False, + default_button_element_size=(12, 1)).Layout(layout) + + # ------ Loop & Process button menu choices ------ # + while True: + event, values = window.Read() + if event == None or event == 'Exit': + break + print('Button = ', event) + # ------ Process menu choices ------ # + if event == 'About...': + sg.Popup('About this program', 'Version 1.0', 'PySimpleGUI rocks...') + elif event == 'Open': + filename = sg.PopupGetFile('file to open', no_window=True) + print(filename) + +## Graphing with Graph Element + +Use the Graph Element to draw points, lines, circles, rectangles using ***your*** coordinate systems rather than the underlying graphics coordinates. + +In this example we're defining our graph to be from -100, -100 to +100,+100. That means that zero is in the middle of the drawing. You define this graph description in your call to Graph. + + +![graph markers](https://user-images.githubusercontent.com/13696193/46113087-01eaa480-c1bb-11e8-9784-0dbb4ce728b0.jpg) + + +``` +import math +import PySimpleGUI as sg + +layout = [[sg.Graph(canvas_size=(400, 400), graph_bottom_left=(-105,-105), graph_top_right=(105,105), background_color='white', key='graph', tooltip='This is a cool graph!')],] + +window = sg.Window('Graph of Sine Function', grab_anywhere=True).Layout(layout).Finalize() +graph = window.FindElement('graph') + +# Draw axis +graph.DrawLine((-100,0), (100,0)) +graph.DrawLine((0,-100), (0,100)) + +for x in range(-100, 101, 20): + graph.DrawLine((x,-3), (x,3)) + if x != 0: + graph.DrawText( x, (x,-10), color='green') + +for y in range(-100, 101, 20): + graph.DrawLine((-3,y), (3,y)) + if y != 0: + graph.DrawText( y, (-10,y), color='blue') + +# Draw Graph +for x in range(-100,100): + y = math.sin(x/20)*50 + graph.DrawCircle((x,y), 1, line_color='red', fill_color='red') + +event, values = window.Read() +``` + + + + + +## Tabs + +Tabs bring not only an extra level of sophistication to your window layout, they give you extra room to add more elements. Tabs are one of the 3 container Elements, Elements that hold or contain other Elements. The other two are the Column and Frame Elements. + + +![tabs](https://user-images.githubusercontent.com/13696193/46049479-97732f00-c0fc-11e8-8015-5bbed8bd88bb.jpg) + + + +``` +import PySimpleGUI as sg + +tab1_layout = [[sg.T('This is inside tab 1')]] + +tab2_layout = [[sg.T('This is inside tab 2')], + [sg.In(key='in')]] + +layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, tooltip='tip'), sg.Tab('Tab 2', tab2_layout)]], tooltip='TIP2')], + [sg.RButton('Read')]] + +window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout) + +while True: + event, values = window.Read() + print(event,values) + if event is None: # always, always give a way out! + break +``` + + + + +## Creating a Windows .EXE File + +It's possible to create a single .EXE file that can be distributed to Windows users. There is no requirement to install the Python interpreter on the PC you wish to run it on. Everything it needs is in the one EXE file, assuming you're running a somewhat up to date version of Windows. + +Installation of the packages, you'll need to install PySimpleGUI and PyInstaller (you need to install only once) + + pip install PySimpleGUI + pip install PyInstaller + +To create your EXE file from your program that uses PySimpleGUI, `my_program.py`, enter this command in your Windows command prompt: + + pyinstaller -wF my_program.py + +You will be left with a single file, `my_program.exe`, located in a folder named `dist` under the folder where you executed the `pyinstaller` command. + +That's all... Run your `my_program.exe` file on the Windows machine of your choosing. + +> "It's just that easy." +> +(famous last words that screw up just about anything being referenced) + +Your EXE file should run without creating a "shell window". Only the GUI window should show up on your taskbar. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index b75f4328..9973699c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,300 +1,302 @@ - - - - -![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) - -[![Downloads](http://pepy.tech/badge/pysimplegui)](http://pepy.tech/project/pysimplegui) -[![Downloads ](https://pepy.tech/badge/pysimplegui27)](https://pepy.tech/project/pysimplegui27) -![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) - ![Awesome Meter](https://img.shields.io/badge/Awesome_meter-100-yellow.svg) - ![Python Version](https://img.shields.io/badge/Python-2.7_3.x-yellow.svg) - - - - - - -# PySimpleGUI - - -## Now supports both Python 2.7 & 3 - -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.9.3-red.svg?longCache=true&style=for-the-badge) - - ![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.1.3-blue.svg?longCache=true&style=for-the-badge) - -[Announcements of Latest Developments](https://github.com/MikeTheWatchGuy/PySimpleGUI/issues/142) - -[ReadTheDocs](http://pysimplegui.readthedocs.io/) - -[COOKBOOK!](https://pysimplegui.readthedocs.io/en/latest/cookbook/) - -[Brief Tutorial](https://pysimplegui.readthedocs.io/en/latest/tutorial/) - -[Latest Demos and Master Branch on GitHub](https://github.com/MikeTheWatchGuy/PySimpleGUI) - -[Docs in PDF Format](https://github.com/MikeTheWatchGuy/PySimpleGUI/tree/master/docs) - - + + + + + + +![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) + +[![Downloads](http://pepy.tech/badge/pysimplegui)](http://pepy.tech/project/pysimplegui) +[![Downloads ](https://pepy.tech/badge/pysimplegui27)](https://pepy.tech/project/pysimplegui27) +![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) + ![Awesome Meter](https://img.shields.io/badge/Awesome_meter-100-yellow.svg) + ![Python Version](https://img.shields.io/badge/Python-2.7_3.x-yellow.svg) + + + + + + +# PySimpleGUI + + +## Now supports both Python 2.7 & 3 + +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.9.3-red.svg?longCache=true&style=for-the-badge) + + ![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.1.3-blue.svg?longCache=true&style=for-the-badge) + +[Announcements of Latest Developments](https://github.com/MikeTheWatchGuy/PySimpleGUI/issues/142) + +[ReadTheDocs](http://pysimplegui.readthedocs.io/) + +[COOKBOOK!](https://pysimplegui.readthedocs.io/en/latest/cookbook/) + +[Brief Tutorial](https://pysimplegui.readthedocs.io/en/latest/tutorial/) + +[Latest Demos and Master Branch on GitHub](https://github.com/MikeTheWatchGuy/PySimpleGUI) + +[Docs in PDF Format](https://github.com/MikeTheWatchGuy/PySimpleGUI/tree/master/docs) + + Super-simple GUI to use... Powerfully customizable - -Home of the 1-line custom GUI & 1-line progress meter + +Home of the 1-line custom GUI & 1-line progress meter The native GUI framework for perfectionists with deadlines - -#### Note regarding Python versions -As of 9/25/2018 **both Python 3 and Python 2.7 are supported**! The Python 3 version is named `PySimpleGUI`. The Python 2.7 version is `PySimpleGUI27`. They are installed separately and the imports are different. See instructions in Installation section for more info. - - ------------------------------------------------------------------------- - - -Looking for a GUI package? -* Taking your Python code from the world of command lines and into the convenience of a GUI? * -* Have a Raspberry **Pi** with a touchscreen that's going to waste because you don't have the time to learn a GUI SDK? -* Into Machine Learning and are sick of the command line? -* Would like to distribute your Python code to Windows users as a single .EXE file that launches straight into a GUI, much like a WinForms app? - -Look no further, **you've found your GUI package**. - - import PySimpleGUI as sg - - sg.Popup('Hello From PySimpleGUI!', 'This is the shortest GUI program ever!') - - -![hello world](https://user-images.githubusercontent.com/13696193/44960047-1f7f6380-aec6-11e8-9d5e-12ef935bcade.jpg) - - -Or how about a ***custom GUI*** in 1 line of code? - - import PySimpleGUI as sg - - button, (filename,) = sg.Window('Get filename example'). Layout([[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ]).Read() - -![get filename](https://user-images.githubusercontent.com/13696193/44960039-f1018880-aec5-11e8-8a43-3d7f8ff93b67.jpg) - - - Build beautiful customized windows that fit your specific problem. Let PySimpleGUI solve your GUI problem while you solve your real problems. Look through the Cookbook, find a matching recipe, copy, paste, run within minutes. This is the process PySimpleGUI was designed to facilitate. - - -![borderless grayed buttons](https://user-images.githubusercontent.com/13696193/45168664-d848e980-b1c9-11e8-886e-63279ae4017f.jpg) - - - -PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkinter, but you interact with them in a more friendly way. It does the layout and boilerplate code for you and presents you with a simple, efficient interface. - -![everything dark theme](https://user-images.githubusercontent.com/13696193/44959854-b1d23800-aec3-11e8-90b6-5af915a86d15.jpg) - -Perhaps you're looking for a way to interact with your **Raspberry Pi** in a more friendly way. The same for shown as on Pi (roughly the same) - -![raspberry pi everything demo](https://user-images.githubusercontent.com/13696193/44279694-5b58ce80-a220-11e8-9ab6-d6021f5a944f.jpg) - - - -In addition to a primary GUI, you can add a Progress Meter to your code with ONE LINE of code. Slide this line into any of your `for` loops and get a nice meter: - - OneLineProgressMeter('My meter title', current_value, max value, 'key') - - ![easyprogressmeter](https://user-images.githubusercontent.com/13696193/44960065-83099100-aec6-11e8-8aa8-96e4b100a0e4.jpg) - -You can build an async media player GUI with custom buttons in 30 lines of code. - -![media player 2](https://user-images.githubusercontent.com/13696193/44960091-eeebf980-aec6-11e8-884e-80d4447a83cd.jpg) - - -How about embedding a game inside of a GUI? This game of Pong is written in tkinter and then dropped into the PySimpleGUI window creating a game that has an accompanying GUI. - -![pong](https://user-images.githubusercontent.com/13696193/45860012-2d8d0b00-bd33-11e8-9efd-3eaf4c30f324.gif) - - -Combining PySimpleGUI with PyInstaller creates something truly remarkable and special, a Python program that looks like a Windows WinForms application. This application with working menu was created in 20 lines of Python code. It is a single .EXE file that launches straight into the screen you see. And more good news, the only icon you see on the taskbar is the window itself... there is no pesky shell window. - -![menu demo](https://user-images.githubusercontent.com/13696193/45923097-8fbc4c00-beaa-11e8-87d2-01a5331811c8.gif) - - - ## Background -I was frustrated by having to deal with the dos prompt when I had a powerful Windows machine right in front of me. Why is it SO difficult to do even the simplest of input/output to a window in Python?? - -There are a number of 'easy to use' Python GUIs, but they were too limited for my requirements. PySimpleGUI aims for the same simplicity found in packages like `EasyGUI`and `WxSimpleGUI` , both really handy but limited, and adds the ability to define your own layouts. This ability to make your own windows using a large palette of widgets is but one difference between the existing "simple" packages and `PySimpleGUI`. - -With a simple GUI, it becomes practical to "associate" .py files with the python interpreter on Windows. Double click a py file and up pops a GUI window, a more pleasant experience than opening a dos Window and typing a command line. - -The `PySimpleGUI` package is focused on the ***developer***. -> Create a custom GUI with as little and as simple code as possible. - -This was the primary focus used to create PySimpleGUI. - -> "Do it in a Python-like way" - -was the second. - -## Features - -While simple to use, PySimpleGUI has significant depth to be explored by more advanced programmers. The feature set goes way beyond the requirements of a beginner programmer, and into the required features needed for complex GUIs. - - Features of PySimpleGUI include: - Support for Python versions 2.7 and 3 - Text - Single Line Input - Buttons including these types: - File Browse - Files Browse - Folder Browse - SaveAs - Non-closing return - Close window - Realtime - Calendar chooser - Color chooser - Checkboxes - Radio Buttons - Listbox - Option Menu - Slider - Graph - Frame with title - Icons - Multi-line Text Input - Scroll-able Output - Images + +#### Note regarding Python versions +As of 9/25/2018 **both Python 3 and Python 2.7 are supported**! The Python 3 version is named `PySimpleGUI`. The Python 2.7 version is `PySimpleGUI27`. They are installed separately and the imports are different. See instructions in Installation section for more info. + + +------------------------------------------------------------------------ + + +Looking for a GUI package? +* Taking your Python code from the world of command lines and into the convenience of a GUI? * +* Have a Raspberry **Pi** with a touchscreen that's going to waste because you don't have the time to learn a GUI SDK? +* Into Machine Learning and are sick of the command line? +* Would like to distribute your Python code to Windows users as a single .EXE file that launches straight into a GUI, much like a WinForms app? + +Look no further, **you've found your GUI package**. + + import PySimpleGUI as sg + + sg.Popup('Hello From PySimpleGUI!', 'This is the shortest GUI program ever!') + + +![hello world](https://user-images.githubusercontent.com/13696193/44960047-1f7f6380-aec6-11e8-9d5e-12ef935bcade.jpg) + + +Or how about a ***custom GUI*** in 1 line of code? + + import PySimpleGUI as sg + + event, (filename,) = sg.Window('Get filename example'). Layout([[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ]).Read() + +![get filename](https://user-images.githubusercontent.com/13696193/44960039-f1018880-aec5-11e8-8a43-3d7f8ff93b67.jpg) + + + Build beautiful customized windows that fit your specific problem. Let PySimpleGUI solve your GUI problem while you solve your real problems. Look through the Cookbook, find a matching recipe, copy, paste, run within minutes. This is the process PySimpleGUI was designed to facilitate. + + +![borderless grayed buttons](https://user-images.githubusercontent.com/13696193/45168664-d848e980-b1c9-11e8-886e-63279ae4017f.jpg) + + + +PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkinter, but you interact with them in a more friendly way. It does the layout and boilerplate code for you and presents you with a simple, efficient interface. + +![everything dark theme](https://user-images.githubusercontent.com/13696193/44959854-b1d23800-aec3-11e8-90b6-5af915a86d15.jpg) + +Perhaps you're looking for a way to interact with your **Raspberry Pi** in a more friendly way. The same for shown as on Pi (roughly the same) + +![raspberry pi everything demo](https://user-images.githubusercontent.com/13696193/44279694-5b58ce80-a220-11e8-9ab6-d6021f5a944f.jpg) + + + +In addition to a primary GUI, you can add a Progress Meter to your code with ONE LINE of code. Slide this line into any of your `for` loops and get a nice meter: + + OneLineProgressMeter('My meter title', current_value, max value, 'key') + + ![easyprogressmeter](https://user-images.githubusercontent.com/13696193/44960065-83099100-aec6-11e8-8aa8-96e4b100a0e4.jpg) + +You can build an async media player GUI with custom buttons in 30 lines of code. + +![media player 2](https://user-images.githubusercontent.com/13696193/44960091-eeebf980-aec6-11e8-884e-80d4447a83cd.jpg) + + +How about embedding a game inside of a GUI? This game of Pong is written in tkinter and then dropped into the PySimpleGUI window creating a game that has an accompanying GUI. + +![pong](https://user-images.githubusercontent.com/13696193/45860012-2d8d0b00-bd33-11e8-9efd-3eaf4c30f324.gif) + + +Combining PySimpleGUI with PyInstaller creates something truly remarkable and special, a Python program that looks like a Windows WinForms application. This application with working menu was created in 20 lines of Python code. It is a single .EXE file that launches straight into the screen you see. And more good news, the only icon you see on the taskbar is the window itself... there is no pesky shell window. + +![menu demo](https://user-images.githubusercontent.com/13696193/45923097-8fbc4c00-beaa-11e8-87d2-01a5331811c8.gif) + + + ## Background +I was frustrated by having to deal with the dos prompt when I had a powerful Windows machine right in front of me. Why is it SO difficult to do even the simplest of input/output to a window in Python?? + +There are a number of 'easy to use' Python GUIs, but they were too limited for my requirements. PySimpleGUI aims for the same simplicity found in packages like `EasyGUI`and `WxSimpleGUI` , both really handy but limited, and adds the ability to define your own layouts. This ability to make your own windows using a large palette of widgets is but one difference between the existing "simple" packages and `PySimpleGUI`. + +With a simple GUI, it becomes practical to "associate" .py files with the python interpreter on Windows. Double click a py file and up pops a GUI window, a more pleasant experience than opening a dos Window and typing a command line. + +The `PySimpleGUI` package is focused on the ***developer***. +> Create a custom GUI with as little and as simple code as possible. + +This was the primary focus used to create PySimpleGUI. + +> "Do it in a Python-like way" + +was the second. + +## Features + +While simple to use, PySimpleGUI has significant depth to be explored by more advanced programmers. The feature set goes way beyond the requirements of a beginner programmer, and into the required features needed for complex GUIs. + + Features of PySimpleGUI include: + Support for Python versions 2.7 and 3 + Text + Single Line Input + Buttons including these types: + File Browse + Files Browse + Folder Browse + SaveAs + Non-closing return + Close window + Realtime + Calendar chooser + Color chooser + Checkboxes + Radio Buttons + Listbox + Option Menu + Slider + Graph + Frame with title + Icons + Multi-line Text Input + Scroll-able Output + Images Tables Trees - Progress Bar Async/Non-Blocking Windows - Tabbed windows - Persistent Windows - Redirect Python Output/Errors to scrolling window - 'Higher level' APIs (e.g. MessageBox, YesNobox, ...) - Single-Line-Of-Code Proress Bar & Debug Print - Complete control of colors, look and feel - Selection of pre-defined palettes - Button images - Return values as dictionary - Set focus - Bind return key to buttons - Group widgets into a column and place into window anywhere - Scrollable columns - Keyboard low-level key capture - Mouse scroll-wheel support - Get Listbox values as they are selected - Get slider, spinner, combo as they are changed - Update elements in a live window - Bulk window-fill operation - Save / Load window to/from disk - Borderless (no titlebar) windows - Always on top windows - Menus with ALT-hotkey - Tooltips - Clickable links - No async programming required (no callbacks to worry about) - - -An example of many widgets used on a single window. A little further down you'll find the 21 lines of code required to create this complex window. Try it if you don't believe it. Install PySimpleGUI then : - ->Start Python, copy and paste the code below into the >>> prompt and hit enter. This will pop up... -> - -![everything example](https://user-images.githubusercontent.com/13696193/43097412-0a4652aa-8e8a-11e8-8e09-939484e3c568.jpg) - - - - import PySimpleGUI as sg - - layout = [[sg.Text('All graphic widgets in one window!', size=(30, 1), font=("Helvetica", 25), text_color='blue')], - [sg.Text('Here is some text.... and a place to enter text')], - [sg.InputText()], - [sg.Checkbox('My first checkbox!'), sg.Checkbox('My second checkbox!', default=True)], - [sg.Radio('My first Radio! ', "RADIO1", default=True), sg.Radio('My second Radio!', "RADIO1")], - [sg.Multiline(default_text='This is the default Text shoulsd you decide not to type anything',)], - [sg.InputCombo(['Combobox 1', 'Combobox 2'], size=(20, 3)), - sg.Slider(range=(1, 100), orientation='h', size=(35, 20), default_value=85)], - [sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6)), - sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=25), - sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=75), - sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=10)], - [sg.Text('_' * 100, size=(70, 1))], - [sg.Text('Choose Source and Destination Folders', size=(35, 1))], - [sg.Text('Source Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Source'), - sg.FolderBrowse()], - [sg.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Dest'), - sg.FolderBrowse()], - [sg.Submit(), sg.Cancel(), sg.Button('Customized', button_color=('white', 'green'))]] - - button, values = sg.Window('Everything bagel', auto_size_text=True, default_element_size=(40, 1)).Layout(layout).Read() - - - ---- -### Design Goals - -> Copy, Paste, Run. - -`PySimpleGUI's` goal with the API is to be easy on the programmer, and to function in a Python-like way. Since GUIs are visual, it was desirable for the code to visually match what's on the screen. By providing a significant amount of documentation and an easy to use Cookbook, it's possible to see your first GUI within 5 minutes of beginning the installation. - - > Be Pythonic - - Be Pythonic... Attempted to use language constructs in a natural way and to exploit some of Python's interesting features. Python's lists and optional parameters make PySimpleGUI work smoothly. - - - windows are represented as Python lists. - - A window is a list of rows - - A row is a list of elements -- Return values are a list of button presses and input values. -- Return values can also be represented as a dictionary -- The SDK calls collapse down into a single line of Python code that presents a custom GUI and returns values -- Linear programming instead of callbacks - - #### Lofty Goals - -> Change Python - -The hope is not that ***this*** package will become part of the Python Standard Library. - -The hope is that Python will become ***the*** go-to language for creating GUI programs that run on Windows, Mac, and Linux *for all levels of developer*. - -The hope is that beginners that are interested in graphic design will have an easy way to express themselves, right from the start of their Python experience. - -There is a noticeable gap in the Python GUI solution. Fill that gap and who knows what will happen. - -Maybe there's no "there there". ***Or*** maybe a simple GUI API will enable Python to dominate yet another computing discipline like it has so many others. This is my attempt to find out. - - - ----- -## Getting Started with PySimpleGUI - -### Installing Python 3 - - pip install --upgrade PySimpleGUI - -On some systems you need to run pip3. - - pip3 install --upgrade PySimpleGUI - -On a Raspberry Pi, this is should work: - - sudo pip3 install --upgrade pysimplegui - -Some users have found that upgrading required using an extra flag on the pip `--no-cache-dir`. - - pip install --upgrade --no-cache-dir - -On some versions of Linux you will need to first install pip. Need the Chicken before you can get the Egg (get it... Egg?) - -`sudo apt install python3-pip ` - -If for some reason you are unable to install using `pip`, don't worry, you can still import PySimpleGUI by downloading the file PySimleGUI.py and placing it in your folder along with the application that is importing it. - -`tkinter` is a requirement for PySimpleGUI (the only requirement). Some OS variants, such as Ubuntu, do not some with `tkinter` already installed. If you get an error similar to: -``` -ImportError: No module named tkinter -``` -then yosudou need to install `tkinter`. Be sure and get the Python 3 version. -` -```sudo apt-get install python3-tk ``` - - -### Installing for Python 2.7 - + Progress Bar Async/Non-Blocking Windows + Tabbed windows + Persistent Windows + Redirect Python Output/Errors to scrolling window + 'Higher level' APIs (e.g. MessageBox, YesNobox, ...) + Single-Line-Of-Code Proress Bar & Debug Print + Complete control of colors, look and feel + Selection of pre-defined palettes + Button images + Return values as dictionary + Set focus + Bind return key to buttons + Group widgets into a column and place into window anywhere + Scrollable columns + Keyboard low-level key capture + Mouse scroll-wheel support + Get Listbox values as they are selected + Get slider, spinner, combo as they are changed + Update elements in a live window + Bulk window-fill operation + Save / Load window to/from disk + Borderless (no titlebar) windows + Always on top windows + Menus with ALT-hotkey + Tooltips + Clickable links + No async programming required (no callbacks to worry about) + + +An example of many widgets used on a single window. A little further down you'll find the 21 lines of code required to create this complex window. Try it if you don't believe it. Install PySimpleGUI then : + +>Start Python, copy and paste the code below into the >>> prompt and hit enter. This will pop up... +> + +![everything example](https://user-images.githubusercontent.com/13696193/43097412-0a4652aa-8e8a-11e8-8e09-939484e3c568.jpg) + + + + import PySimpleGUI as sg + + layout = [[sg.Text('All graphic widgets in one window!', size=(30, 1), font=("Helvetica", 25), text_color='blue')], + [sg.Text('Here is some text.... and a place to enter text')], + [sg.InputText()], + [sg.Checkbox('My first checkbox!'), sg.Checkbox('My second checkbox!', default=True)], + [sg.Radio('My first Radio! ', "RADIO1", default=True), sg.Radio('My second Radio!', "RADIO1")], + [sg.Multiline(default_text='This is the default Text shoulsd you decide not to type anything',)], + [sg.InputCombo(['Combobox 1', 'Combobox 2'], size=(20, 3)), + sg.Slider(range=(1, 100), orientation='h', size=(35, 20), default_value=85)], + [sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6)), + sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=25), + sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=75), + sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=10)], + [sg.Text('_' * 100, size=(70, 1))], + [sg.Text('Choose Source and Destination Folders', size=(35, 1))], + [sg.Text('Source Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Source'), + sg.FolderBrowse()], + [sg.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Dest'), + sg.FolderBrowse()], + [sg.Submit(), sg.Cancel(), sg.Button('Customized', button_color=('white', 'green'))]] + + event, values = sg.Window('Everything bagel', auto_size_text=True, default_element_size=(40, 1)).Layout(layout).Read() + + + +--- +### Design Goals + +> Copy, Paste, Run. + +`PySimpleGUI's` goal with the API is to be easy on the programmer, and to function in a Python-like way. Since GUIs are visual, it was desirable for the code to visually match what's on the screen. By providing a significant amount of documentation and an easy to use Cookbook, it's possible to see your first GUI within 5 minutes of beginning the installation. + + > Be Pythonic + + Be Pythonic... Attempted to use language constructs in a natural way and to exploit some of Python's interesting features. Python's lists and optional parameters make PySimpleGUI work smoothly. + + - windows are represented as Python lists. + - A window is a list of rows + - A row is a list of elements +- Return values are a list of button presses and input values. +- Return values can also be represented as a dictionary +- The SDK calls collapse down into a single line of Python code that presents a custom GUI and returns values +- Linear programming instead of callbacks + + #### Lofty Goals + +> Change Python + +The hope is not that ***this*** package will become part of the Python Standard Library. + +The hope is that Python will become ***the*** go-to language for creating GUI programs that run on Windows, Mac, and Linux *for all levels of developer*. + +The hope is that beginners that are interested in graphic design will have an easy way to express themselves, right from the start of their Python experience. + +There is a noticeable gap in the Python GUI solution. Fill that gap and who knows what will happen. + +Maybe there's no "there there". ***Or*** maybe a simple GUI API will enable Python to dominate yet another computing discipline like it has so many others. This is my attempt to find out. + + + ----- +## Getting Started with PySimpleGUI + +### Installing Python 3 + + pip install --upgrade PySimpleGUI + +On some systems you need to run pip3. + + pip3 install --upgrade PySimpleGUI + +On a Raspberry Pi, this is should work: + + sudo pip3 install --upgrade pysimplegui + +Some users have found that upgrading required using an extra flag on the pip `--no-cache-dir`. + + pip install --upgrade --no-cache-dir + +On some versions of Linux you will need to first install pip. Need the Chicken before you can get the Egg (get it... Egg?) + +`sudo apt install python3-pip ` + +If for some reason you are unable to install using `pip`, don't worry, you can still import PySimpleGUI by downloading the file PySimleGUI.py and placing it in your folder along with the application that is importing it. + +`tkinter` is a requirement for PySimpleGUI (the only requirement). Some OS variants, such as Ubuntu, do not some with `tkinter` already installed. If you get an error similar to: +``` +ImportError: No module named tkinter +``` +then yosudou need to install `tkinter`. Be sure and get the Python 3 version. +` +```sudo apt-get install python3-tk ``` + + +### Installing for Python 2.7 + ` pip install --upgrade PySimpleGUI27` or ` pip2 install --upgrade PySimpleGUI27` @@ -302,173 +304,173 @@ then yosudou need to install `tkinter`. Be sure and get the Python 3 version. You may need to also install "future" for version 2.7 pip install future - or + or ` pip2 install future` -Python 2.7 support is relatively new and the bugs are still being worked out. I'm unsure what may need to be done to install tkinter for Python 2.7. Will update this readme when more info is available - -Like above, you may have to install either pip or tkinter. To do this on Python 2.7: - -`sudo apt install python-pip` - -`sudo apt install python-tkinter` +Python 2.7 support is relatively new and the bugs are still being worked out. I'm unsure what may need to be done to install tkinter for Python 2.7. Will update this readme when more info is available + +Like above, you may have to install either pip or tkinter. To do this on Python 2.7: + +`sudo apt install python-pip` + +`sudo apt install python-tkinter` + + + +### Testing your installation + +Once you have installed, or copied the .py file to your app folder, you can test the installation using python. At the command prompt start up Python. + +#### Instructions for Python 2.7: +``` +python +>>> import PySimpleGUI27 +>>> PySimpleGUI27.main() +``` + +#### Instructions for Python 3: + +``` +python3 +>>> import PySimpleGUI +>>> PySimpleGUI.main() +``` + +You will see a sample window in the center of your screen. If it's not installed correctly you are likely to get an error message during one of those commands + +Here is the window you should see: + +![sample window](https://user-images.githubusercontent.com/13696193/46097669-79efa500-c190-11e8-885c-e5d4d5d09ea6.jpg) + + + +### Prerequisites +Python 2.7 or Python 3 +tkinter + +PySimpleGUI Runs on all Python3 platforms that have tkinter running on them. It has been tested on Windows, Mac, Linux, Raspberry Pi. Even runs on `pypy3`. + +### EXE file creation + +If you wish to create an EXE from your PySimpleGUI application, you will need to install `PyInstaller`. There are instructions on how to create an EXE at the bottom of this ReadMe + + +## Using - Python 3 + +To use in your code, simply import.... + `import PySimpleGUI as sg` + +Then use either "high level" API calls or build your own windows. + + sg.Popup('This is my first Popup') + +![first popup](https://user-images.githubusercontent.com/13696193/44957300-c7813680-ae9e-11e8-9a8c-c70198db7907.jpg) + + +Yes, it's just that easy to have a window appear on the screen using Python. With PySimpleGUI, making a custom window appear isn't much more difficult. The goal is to get you running on your GUI within ***minutes***, not hours nor days. + +## Using - Python 2.7 + +Those using Python 2.7 will import a different module name + `import PySimpleGUI27 as sg` + +## Code Samples Assume Python 3 + +While all of the code examples you will see in this Readme and the Cookbook assume Python 3 and thus have an `import PySimpleGUI` at the top, you can run ***all*** of this code on Python 2.7 by changing the import statement to `import PySimpleGUI27` + +--- +## APIs + +PySimpleGUI can be broken down into 2 types of API's: + * High Level single call functions (The `Popup` calls) + * Custom window functions + + +### Python Language Features + + There are a number of Python language features that PySimpleGUI utilizes heavily for API access that should be understood... + * Variable number of arguments to a function call + * Optional parameters to a function call + * Dictionaries + +#### Variable Number of Arguments + + The "High Level" API calls that *output* values take a variable number of arguments so that they match a "print" statement as much as possible. The idea is to make it simple for the programmer to output as many items as desired and in any format. The user need not convert the variables to be output into the strings. The PySimpleGUI functions do that for the user. + + sg.Popup('Variable number of parameters example', var1, var2, "etc") + +Each new item begins on a new line in the Popup + + ![snap0179](https://user-images.githubusercontent.com/13696193/43658129-f6ca49c6-9725-11e8-9317-1f77443eb04a.jpg) + + + +#### Optional Parameters to a Function Call + +This feature of the Python language is utilized ***heavily*** as a method of customizing windows and window Elements. Rather than requiring the programmer to specify every possible option for a widget, instead only the options the caller wants to override are specified. + +Here is the function definition for the Popup function. The details aren't important. What is important is seeing that there is a long list of potential tweaks that a caller can make. However, they don't *have* to be specified on each and every call. + + def Popup(*args, + button_color=None, + button_type=MSG_BOX_OK, + auto_close=False, + auto_close_duration=None, + icon=DEFAULT_WINDOW_ICON, + line_width=MESSAGE_BOX_LINE_WIDTH, + font=None): + +If the caller wanted to change the button color to be black on yellow, the call would look something like this: + + sg.Popup('This box has a custom button color', button_color=('black', 'yellow')) + + +![snap0180](https://user-images.githubusercontent.com/13696193/43658171-13a72bfe-9726-11e8-8c7a-0a46e46fb202.jpg) + + +#### Dictionaries + +Dictionaries are used by more advanced PySimpleGUI users. You'll know that dictionaries are being used if you see a `key` parameter on any Element. Dictionaries are used in 2 ways: +1. To identify values when a window is read +2. To identify Elements so that they can be "updated" + +--- + +## High Level API Calls - Popup's + +"High level calls" are those that start with "Popup". They are the most basic form of communications with the user. They are named after the type of window they create, a pop-up window. These windows are meant to be short lived while, either delivering information or collecting it, and then quickly disappearing. + +## Popup Output + +Think of the `Popup` call as the GUI equivalent of a `print` statement. It's your way of displaying results to a user in the windowed world. Each call to Popup will create a new Popup window. + +`Popup` calls are normally blocking. your program will stop executing until the user has closed the Popup window. A non-blocking window of Popup discussed in the async section. + +Just like a print statement, you can pass any number of arguments you wish. They will all be turned into strings and displayed in the popup window. + +There are a number of Popup output calls, each with a slightly different look (e.g. different button labels). + +The list of Popup output functions are + + Popup + PopupOk + PopupYesNo + PopupCancel + PopupOkCancel + PopupError + PopupTimed, PopupAutoClose + PopupNoWait, PopupNonBlocking + +The trailing portion of the function name after Popup indicates what buttons are shown. `PopupYesNo` shows a pair of button with Yes and No on them. `PopupCancel` has a Cancel button, etc. + +While these are "output" windows, they do collect input in the form of buttons. The Popup functions return the button that was clicked. If the Ok button was clicked, then Popup returns the string 'Ok'. If the user clicked the X button to close the window, then the button value returned is `None`. + +The function `PopupTimed` or `PopupAutoClose` are popup windows that will automatically close after come period of time. + +Here is a quick-reference showing how the Popup calls look. - -### Testing your installation - -Once you have installed, or copied the .py file to your app folder, you can test the installation using python. At the command prompt start up Python. - -#### Instructions for Python 2.7: -``` -python ->>> import PySimpleGUI27 ->>> PySimpleGUI27.main() -``` - -#### Instructions for Python 3: - -``` -python3 ->>> import PySimpleGUI ->>> PySimpleGUI.main() -``` - -You will see a sample window in the center of your screen. If it's not installed correctly you are likely to get an error message during one of those commands - -Here is the window you should see: - -![sample window](https://user-images.githubusercontent.com/13696193/46097669-79efa500-c190-11e8-885c-e5d4d5d09ea6.jpg) - - - -### Prerequisites -Python 2.7 or Python 3 -tkinter - -PySimpleGUI Runs on all Python3 platforms that have tkinter running on them. It has been tested on Windows, Mac, Linux, Raspberry Pi. Even runs on `pypy3`. - -### EXE file creation - -If you wish to create an EXE from your PySimpleGUI application, you will need to install `PyInstaller`. There are instructions on how to create an EXE at the bottom of this ReadMe - - -## Using - Python 3 - -To use in your code, simply import.... - `import PySimpleGUI as sg` - -Then use either "high level" API calls or build your own windows. - - sg.Popup('This is my first Popup') - -![first popup](https://user-images.githubusercontent.com/13696193/44957300-c7813680-ae9e-11e8-9a8c-c70198db7907.jpg) - - -Yes, it's just that easy to have a window appear on the screen using Python. With PySimpleGUI, making a custom window appear isn't much more difficult. The goal is to get you running on your GUI within ***minutes***, not hours nor days. - -## Using - Python 2.7 - -Those using Python 2.7 will import a different module name - `import PySimpleGUI27 as sg` - -## Code Samples Assume Python 3 - -While all of the code examples you will see in this Readme and the Cookbook assume Python 3 and thus have an `import PySimpleGUI` at the top, you can run ***all*** of this code on Python 2.7 by changing the import statement to `import PySimpleGUI27` - ---- -## APIs - -PySimpleGUI can be broken down into 2 types of API's: - * High Level single call functions (The `Popup` calls) - * Custom window functions - - -### Python Language Features - - There are a number of Python language features that PySimpleGUI utilizes heavily for API access that should be understood... - * Variable number of arguments to a function call - * Optional parameters to a function call - * Dictionaries - -#### Variable Number of Arguments - - The "High Level" API calls that *output* values take a variable number of arguments so that they match a "print" statement as much as possible. The idea is to make it simple for the programmer to output as many items as desired and in any format. The user need not convert the variables to be output into the strings. The PySimpleGUI functions do that for the user. - - sg.Popup('Variable number of parameters example', var1, var2, "etc") - -Each new item begins on a new line in the Popup - - ![snap0179](https://user-images.githubusercontent.com/13696193/43658129-f6ca49c6-9725-11e8-9317-1f77443eb04a.jpg) - - - -#### Optional Parameters to a Function Call - -This feature of the Python language is utilized ***heavily*** as a method of customizing windows and window Elements. Rather than requiring the programmer to specify every possible option for a widget, instead only the options the caller wants to override are specified. - -Here is the function definition for the Popup function. The details aren't important. What is important is seeing that there is a long list of potential tweaks that a caller can make. However, they don't *have* to be specified on each and every call. - - def Popup(*args, - button_color=None, - button_type=MSG_BOX_OK, - auto_close=False, - auto_close_duration=None, - icon=DEFAULT_WINDOW_ICON, - line_width=MESSAGE_BOX_LINE_WIDTH, - font=None): - -If the caller wanted to change the button color to be black on yellow, the call would look something like this: - - sg.Popup('This box has a custom button color', button_color=('black', 'yellow')) - - -![snap0180](https://user-images.githubusercontent.com/13696193/43658171-13a72bfe-9726-11e8-8c7a-0a46e46fb202.jpg) - - -#### Dictionaries - -Dictionaries are used by more advanced PySimpleGUI users. You'll know that dictionaries are being used if you see a `key` parameter on any Element. Dictionaries are used in 2 ways: -1. To identify values when a window is read -2. To identify Elements so that they can be "updated" - ---- - -## High Level API Calls - Popup's - -"High level calls" are those that start with "Popup". They are the most basic form of communications with the user. They are named after the type of window they create, a pop-up window. These windows are meant to be short lived while, either delivering information or collecting it, and then quickly disappearing. - -## Popup Output - -Think of the `Popup` call as the GUI equivalent of a `print` statement. It's your way of displaying results to a user in the windowed world. Each call to Popup will create a new Popup window. - -`Popup` calls are normally blocking. your program will stop executing until the user has closed the Popup window. A non-blocking window of Popup discussed in the async section. - -Just like a print statement, you can pass any number of arguments you wish. They will all be turned into strings and displayed in the popup window. - -There are a number of Popup output calls, each with a slightly different look (e.g. different button labels). - -The list of Popup output functions are - - Popup - PopupOk - PopupYesNo - PopupCancel - PopupOkCancel - PopupError - PopupTimed, PopupAutoClose - PopupNoWait, PopupNonBlocking - -The trailing portion of the function name after Popup indicates what buttons are shown. `PopupYesNo` shows a pair of button with Yes and No on them. `PopupCancel` has a Cancel button, etc. - -While these are "output" windows, they do collect input in the form of buttons. The Popup functions return the button that was clicked. If the Ok button was clicked, then Popup returns the string 'Ok'. If the user clicked the X button to close the window, then the button value returned is `None`. - -The function `PopupTimed` or `PopupAutoClose` are popup windows that will automatically close after come period of time. - -Here is a quick-reference showing how the Popup calls look. - - sg.Popup('Popup') - Shows OK button sg.PopupOk('PopupOk') - Shows OK button sg.PopupYesNo('PopupYesNo') - Shows Yes and No buttons @@ -477,42 +479,42 @@ Here is a quick-reference showing how the Popup calls look. sg.PopupError('PopupError') - Shows red error button sg.PopupTimed('PopupTimed') - Automatically closes sg.PopupAutoClose('PopupAutoClose') - Same as PopupTimed - - - -![snap0256](https://user-images.githubusercontent.com/13696193/44957394-1380ab00-aea0-11e8-98b1-1ab7d7bd5b37.jpg) - -![snap0257](https://user-images.githubusercontent.com/13696193/44957400-167b9b80-aea0-11e8-9d42-2314f24e62de.jpg) - -![snap0258](https://user-images.githubusercontent.com/13696193/44957399-154a6e80-aea0-11e8-9580-e716f839d400.jpg) - -![snap0259](https://user-images.githubusercontent.com/13696193/44957398-14b1d800-aea0-11e8-9e88-c2b36a248447.jpg) - -![snap0260](https://user-images.githubusercontent.com/13696193/44957397-14b1d800-aea0-11e8-950b-6d0b4f33841a.jpg) - -![snap0261](https://user-images.githubusercontent.com/13696193/44957396-14194180-aea0-11e8-8eef-bb2e1193ecfa.jpg) - -![snap0264](https://user-images.githubusercontent.com/13696193/44957595-9e15da00-aea1-11e8-8909-6b6121b74509.jpg) - + + + +![snap0256](https://user-images.githubusercontent.com/13696193/44957394-1380ab00-aea0-11e8-98b1-1ab7d7bd5b37.jpg) + +![snap0257](https://user-images.githubusercontent.com/13696193/44957400-167b9b80-aea0-11e8-9d42-2314f24e62de.jpg) + +![snap0258](https://user-images.githubusercontent.com/13696193/44957399-154a6e80-aea0-11e8-9580-e716f839d400.jpg) + +![snap0259](https://user-images.githubusercontent.com/13696193/44957398-14b1d800-aea0-11e8-9e88-c2b36a248447.jpg) + +![snap0260](https://user-images.githubusercontent.com/13696193/44957397-14b1d800-aea0-11e8-950b-6d0b4f33841a.jpg) + +![snap0261](https://user-images.githubusercontent.com/13696193/44957396-14194180-aea0-11e8-8eef-bb2e1193ecfa.jpg) + +![snap0264](https://user-images.githubusercontent.com/13696193/44957595-9e15da00-aea1-11e8-8909-6b6121b74509.jpg) + Popup(*args, Variable number of arguments you want to display - button_color=None, Color of buttons (text_color, background_color) - background_color=None, Color of background - text_color=None, Color of text - button_type=POPUP_BUTTONS_OK, Type of button layout - auto_close=False, If True window will automatically close - auto_close_duration=None, Number of seconds for autoclose - non_blocking=False, If True returns immediately - icon=DEFAULT_WINDOW_ICON, Icon to use on the taskbar - line_width=None, Width of lines in characters - font=None, Font to use for characters - no_titlebar=False, If True no titlebar will be shown - grab_anywhere=False, If True can move window by grabbing anywhere - keep_on_top=False, If True window will be on top of other windows - location=(None,None)): (x,y) coordinates to show the window + button_color=None, Color of buttons (text_color, background_color) + background_color=None, Color of background + text_color=None, Color of text + button_type=POPUP_BUTTONS_OK, Type of button layout + auto_close=False, If True window will automatically close + auto_close_duration=None, Number of seconds for autoclose + non_blocking=False, If True returns immediately + icon=DEFAULT_WINDOW_ICON, Icon to use on the taskbar + line_width=None, Width of lines in characters + font=None, Font to use for characters + no_titlebar=False, If True no titlebar will be shown + grab_anywhere=False, If True can move window by grabbing anywhere + keep_on_top=False, If True window will be on top of other windows + location=(None,None)): (x,y) coordinates to show the window -The other output Popups are variations on parameters. Usually the button_type parameter is the primary one changed. +The other output Popups are variations on parameters. Usually the button_type parameter is the primary one changed. The choices for button_type are: ``` @@ -526,89 +528,89 @@ 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 PopupYesNo 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. - - sg.PopupScrolled(my_text) - -![scrolledtextbox 2](https://user-images.githubusercontent.com/13696193/43667324-712aa0d4-9745-11e8-83a9-a0d0570d0865.jpg) - - -The `PopupScrolled` 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.PopupScrolled(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. - -### PopupNoWait - -The Popup call PopupNoWait or PopupNonBlocking will create a popup window and then immediately return control back to you. All other popup functions will block, waiting for the user to close the popup window. - +#### Scrolled Output +There is a scrolled version of Popups should you have a lot of information to display. + + sg.PopupScrolled(my_text) + +![scrolledtextbox 2](https://user-images.githubusercontent.com/13696193/43667324-712aa0d4-9745-11e8-83a9-a0d0570d0865.jpg) + + +The `PopupScrolled` 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.PopupScrolled(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. + +### PopupNoWait + +The Popup call PopupNoWait or PopupNonBlocking will create a popup window and then immediately return control back to you. All other popup functions will block, waiting for the user to close the popup window. + 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 Input - + + + +## Popup Input + There are Popup calls for single-item inputs. These follow the pattern of `Popup` followed by `Get` and then the type of item to get. There are 3 of these input Popups to choose from, each with settings enabling customization. - - - `PopupGetString` - get a single line of text - - `PopupGetFile` - get a filename - - `PopupGetFolder` - get a folder name - + + - `PopupGetString` - get a single line of text + - `PopupGetFile` - get a filename + - `PopupGetFolder` - 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. - -### PopupGetText + +### PopupGetText Use this Popup to get a ssingle line of text from the user. ``` PopupGetText(message,The message you wish to display with the input field - default_text='', Text to initially fill into the input field - password_char='', Passwork character if this is a password field - size=(None,None), Size of the window - button_color=None, Color to use for buttons (foreground, background) - background_color=None, Background color for window - text_color=None, Text color for window - icon=DEFAULT_WINDOW_ICON, Icon to display on taskbar - font=None, Font to use for text - no_titlebar=False, If True no titlebar will be shown - grab_anywhere=False, If True can grab anywhere to move the window - keep_on_top=False, If True window will stay on top of other windows - location=(None,None)) Location on screen to display window -``` + default_text='', Text to initially fill into the input field + password_char='', Passwork character if this is a password field + size=(None,None), Size of the window + button_color=None, Color to use for buttons (foreground, background) + background_color=None, Background color for window + text_color=None, Text color for window + icon=DEFAULT_WINDOW_ICON, Icon to display on taskbar + font=None, Font to use for text + no_titlebar=False, If True no titlebar will be shown + grab_anywhere=False, If True can grab anywhere to move the window + keep_on_top=False, If True window will stay on top of other windows + location=(None,None)) Location on screen to display window +``` - import PySimpleGUI as sg - - text = sg.PopupGetText('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) - -### PopupGetFile + import PySimpleGUI as sg + + text = sg.PopupGetText('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) + +### PopupGetFile Gets a filename from the user. There are options to configure the type of dialog box to show. Normally an "Open File" dialog box is shown ``` PopupGetFile(message, Message to show in the window - default_path='', Path browsing should start from - default_extension='', Which filetype is the default - save_as=False, Determines which dialog box stype to show - file_types=(("ALL Files", "*.*"),), Which filetypes are displayed - no_window=False, if True no window is displayed except the dialog box - size=(None,None), Size of window - button_color=None, Color of buttons - background_color=None, Color of window background - text_color=None, Color of text in window - icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar - font=None, Font to use - no_titlebar=False, If True does not display a titlebar - grab_anywhere=False, if True can grab window anywhere to move it - keep_on_top=False, if True window will be on top of others - location=(None,None)) Location on screen to show window + default_path='', Path browsing should start from + default_extension='', Which filetype is the default + save_as=False, Determines which dialog box stype to show + file_types=(("ALL Files", "*.*"),), Which filetypes are displayed + no_window=False, if True no window is displayed except the dialog box + size=(None,None), Size of window + button_color=None, Color of buttons + background_color=None, Color of window background + text_color=None, Color of text in window + icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar + font=None, Font to use + no_titlebar=False, If True does not display a titlebar + grab_anywhere=False, if True can grab window anywhere to move it + keep_on_top=False, if True window will be on top of others + location=(None,None)) Location on screen to show window ``` If configured as an Open File Popup then (save_as is not True) the dialog box will look like this @@ -626,618 +628,668 @@ If you choose a filename that already exists, you'll get a warning popup box ask A typical call produces this window. - text = sg.PopupGetFile('Please enter a file name') - sg.Popup('Results', 'The value returned from PopupGetFile', text) - -![popupgetfile](https://user-images.githubusercontent.com/13696193/44957857-2fd31680-aea5-11e8-8eb7-f6b91c202cc8.jpg) - + text = sg.PopupGetFile('Please enter a file name') + sg.Popup('Results', 'The value returned from PopupGetFile', text) + +![popupgetfile](https://user-images.githubusercontent.com/13696193/44957857-2fd31680-aea5-11e8-8eb7-f6b91c202cc8.jpg) + ### PopupGetFolder -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. `PopupGetFile` shows an Open File dialog box while `PopupGetFolder` shows an Open Folder dialog box. +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. `PopupGetFile` shows an Open File dialog box while `PopupGetFolder` shows an Open Folder dialog box. ``` PopupGetFolder(message, Message to display in window - default_path='', Path to start browsing - no_window=False, If True no window will be shown - size=(None,None), Size of window - button_color=None, Color of buttons - background_color=None, Background color of window - text_color=None, Color of window text - icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar - font=None, Font to use for window - no_titlebar=False, If True no titlebar will be shown - grab_anywhere=False, If True can grab anywhere on window to move - keep_on_top=False, If True window will be on top - location=(None, None)) Location on screen to create window + default_path='', Path to start browsing + no_window=False, If True no window will be shown + size=(None,None), Size of window + button_color=None, Color of buttons + background_color=None, Background color of window + text_color=None, Color of window text + icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar + font=None, Font to use for window + no_titlebar=False, If True no titlebar will be shown + grab_anywhere=False, If True can grab anywhere on window to move + keep_on_top=False, If True window will be on top + location=(None, None)) Location on screen to create window ``` This is a typpical call - - text = sg.PopupGetFolder('Please enter a folder name') - sg.Popup('Results', 'The value returned from PopupGetFolder', text) - -![popupgetfolder](https://user-images.githubusercontent.com/13696193/44957861-45484080-aea5-11e8-926c-cf607a45251c.jpg) - + + text = sg.PopupGetFolder('Please enter a folder name') + sg.Popup('Results', 'The value returned from PopupGetFolder', text) + +![popupgetfolder](https://user-images.githubusercontent.com/13696193/44957861-45484080-aea5-11e8-926c-cf607a45251c.jpg) -## 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? - - - OneLineProgressMeter(title, - current_value, - max_value, - key, - *args, - orientation=None, - bar_color=DEFAULT_PROGRESS_BAR_COLOR, - button_color=None, - size=DEFAULT_PROGRESS_BAR_SIZE, - border_width=DEFAULT_PROGRESS_BAR_BORDER_WIDTH): - -Here's the one-line Progress Meter in action! - - for i in range(1,10000): - sg.OneLineProgressMeter('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 `OneLineProgressMeter`. 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 -Another call in the 'Easy' families of APIs is `EasyPrint`. It will 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 a 'print' call in your code. You can even replace your 'print' calls with calls to EasyPrint by simply sticking the statement - - print = sg.EasyPrint - -at the top of your code. -There are a number of names for the same EasyPrint function. `Print` is one of the better ones to use as it's easy to remember. It is simply `print` with a capital P. - - 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: - - import PySimpleGUI as sg - - print=sg.Print - for i in range(100): - print(i) - -Just like the standard print call, `EasyPrint` supports the `sep` and `end` keyword arguments. Other names that can be used to call `EasyPrint` include `Print`, `eprint`, If you want to close the window, call the function `EasyPrintClose`. - -You can change the size of the debug window using the `SetOptions` call with the `debug_win_size` parameter. +## 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? + + + OneLineProgressMeter(title, + current_value, + max_value, + key, + *args, + orientation=None, + bar_color=DEFAULT_PROGRESS_BAR_COLOR, + button_color=None, + size=DEFAULT_PROGRESS_BAR_SIZE, + border_width=DEFAULT_PROGRESS_BAR_BORDER_WIDTH): + +Here's the one-line Progress Meter in action! + + for i in range(1,10000): + sg.OneLineProgressMeter('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 `OneLineProgressMeter`. 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 +Another call in the 'Easy' families of APIs is `EasyPrint`. It will 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 a 'print' call in your code. You can even replace your 'print' calls with calls to EasyPrint by simply sticking the statement + + print = sg.EasyPrint + +at the top of your code. +There are a number of names for the same EasyPrint function. `Print` is one of the better ones to use as it's easy to remember. It is simply `print` with a capital P. + + 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: + + import PySimpleGUI as sg + + print=sg.Print + for i in range(100): + print(i) + +Just like the standard print call, `EasyPrint` supports the `sep` and `end` keyword arguments. Other names that can be used to call `EasyPrint` include `Print`, `eprint`, If you want to close the window, call the function `EasyPrintClose`. + +You can change the size of the debug window using the `SetOptions` call with the `debug_win_size` parameter. *A word of caution.* There are known problems when multiple PySimpleGUI windows are opened. If you open one of these debug windows, if you close it using the Quit button, it can have the side-effect of causing other visible windows to also close. It's a known architectural issue. - ---- -# 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-persistant 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. When this happens, the window will be automatically closed. - -Two other types of windows exist. -1. Persistent window - rather than closing on button clicks, the show window function returns and the window continues to be visible. This is good for applications like a chat window. -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. - -It's both not enjoyable nor helpful to immediately jump into tweaking each and every little thing available to you. - -## 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. - - import PySimpleGUI as sg - - layout = [[sg.Text('Enter a Number')], - [sg.Input()], - [sg.OK()] ] - - button, (number,) = sg.Window('Enter a number example').Layout(layout).Read() - - sg.Popup(button, number) - -### 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. - - import PySimpleGUI as sg - - layout = [[sg.Text('Filename')], - [sg.Input(), sg.FileBrowse()], - [sg.OK(), sg.Cancel()] ] - - button, (number,) = sg.Window('Get filename example').Layout(layout).Read() - - sg.Popup(button, number) - - -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. - - +--- +# 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-persistant 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. When this happens, the window will be automatically closed. + +Two other types of windows exist. +1. Persistent window - rather than closing on button clicks, the show window function returns and the window continues to be visible. This is good for applications like a chat window. +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. + +It's both not enjoyable nor helpful to immediately jump into tweaking each and every little thing available to you. + +## 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. + + import PySimpleGUI as sg + + layout = [[sg.Text('Enter a Number')], + [sg.Input()], + [sg.OK()] ] + + event, (number,) = sg.Window('Enter a number example').Layout(layout).Read() + + sg.Popup(event, number) + +### 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. + + import PySimpleGUI as sg + + layout = [[sg.Text('Filename')], + [sg.Input(), sg.FileBrowse()], + [sg.OK(), sg.Cancel()] ] + + event, (number,) = sg.Window('Get filename example').Layout(layout).Read() + + sg.Popup(event, number) + + +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. + + ## Pattern 1 - Read into list or dictionary (**The Most Common** Pattern) - + This will be the most common pattern you'll follow if you are not using an "event loop" (not reading the window multiple times) It's unusual to assign the values returned from the read call directly into user variables. Usually the variables are grouped together into a list or dictionary of multiple return values. ```python -import PySimpleGUI as sg +import PySimpleGUI as sg + +window_rows = [[sg.Text('SHA-1 and SHA-256 Hashes for the file')], + [sg.InputText(), sg.FileBrowse()], + [sg.Submit(), sg.Cancel()]] -window_rows = [[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(window_rows) -button, values = window.Read() +event, values = window.Read() source_filename = values[0] ``` -## Pattern 2 - 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. +## Pattern 2 - 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 - -layout = [[sg.Text('Persistent window')], - [sg.Input()], - [sg.RButton('Read'), sg.Exit()]] - -window = sg.Window('Window that stays open').Layout(layout) - -while True: - button, values = window.Read() - if button is None or button == 'Exit': - break - print(button, values) +import PySimpleGUI as sg + +layout = [[sg.Text('Persistent window')], + [sg.Input()], + [sg.RButton('Read'), sg.Exit()]] + +window = sg.Window('Window that stays open').Layout(layout) + +while True: + event, values = window.Read() + if event is None or event == 'Exit': + break + print(event, values) ``` - - + + ### 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 this goal is debatable, but it was an attempt just the same. - + +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 this goal is debatable, but it was an attempt just the same. + The key to custom windows in PySimpleGUI is to view windows as ROWS of 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 + + Let's dissect this little program ```python - import PySimpleGUI as sg - - 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.FolderBrowse()], - [sg.Submit(), sg.Cancel()]] - - window = sg.Window('Rename Files or Folders') - - button, values = window.Layout(layout).Read() + import PySimpleGUI as sg + + 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.FolderBrowse()], + [sg.Submit(), sg.Cancel()]] + + window = sg.Window('Rename Files or Folders') + + event, values = window.Layout(layout).Read() ``` - - -![snap0131](https://user-images.githubusercontent.com/13696193/43417007-df6d8408-9407-11e8-9986-30f0415f08a5.jpg) - -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: - - 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.FolderBrowse()], - [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 list with 2 values in it. + + +![snap0131](https://user-images.githubusercontent.com/13696193/43417007-df6d8408-9407-11e8-9986-30f0415f08a5.jpg) + +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: + + 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.FolderBrowse()], + [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 list with 2 values in it. ```python - button, values = window.Read() + event, values = window.Read() folder_path, file_path = values ``` - + In one statement we both show the window and read the user's inputs. In the next the *list* 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 - - As of version 2.8 there are 2 forms of return values, list and dictionary. - -### Return values as a list - - By default return values are a list of values, one entry for each input field. - - Return information from Window, PSG's primary window builder interface, is in this format: - - button, (value1, value2, ...) - -Each of the Elements that are Input Elements will have a value in the list of return values. You can unpack your GUI directly into the variables you want to use. - - button, (filename, folder1, folder2, should_overwrite) = sg.Window('My title').Layout(window_rows).Read() - - Or, more commonly, you can unpack the return results separately. - + + +## Return values + + As of version 2.8 there are 2 forms of return values, list and dictionary. + +### Two Return Values + +All Window Read and ReadNonBlocking calls return 2 values. By convention a read statement is written: ```python -button, values = sg.Window('My title').Layout(window_rows).Read() -button, value_list = window.Layout(window_rows).Read() -value1 = value_list[0] -value2 = value_list[1] - ... -``` - -### Return values as a dictionary - -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: - +event, values = window.Read() +``` +All of the demo programs and the Cookbook recipes have this line of code for windows that are normal reads (not non-blocking). A similar line of code is used for non-blocking window reads: +```python +event, values = window.ReadNonBlocking() +``` + +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 these statements. + +The first parameter `event` describes **why** the read completed. What was the 'event' that caused us to return from reading the window. 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 + + +***Most*** of the time the event will be a button click or the window was closed. + +Another convention to follow is the check for windows being closed with an X. This is an important event to catch. If you don't check for this and you attempt to use the window, your program will crash. Please check for closed window and exit your program gracefully. + +To check for a closed window use this line of code: + + if event is None: + + +### 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. + +Each of the Elements that are Input Elements will have a value in the list of return values. You can unpack your GUI directly into the variables you want to use. + + event, (filename, folder1, folder2, should_overwrite) = sg.Window('My title').Layout(window_rows).Read() + + Or, more commonly, you can unpack the return results separately. + +```python +event, values = sg.Window('My title').Layout(window_rows).Read() +event, value_list = window.Layout(window_rows).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 / advanced method is to request your values be returned as a dictionary. + +### Return values as a dictionary + +For those of you that have not encountered a Python dictionary, don't freak out! Just copy and paste this 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(layout).Read() - -All of your return values will be stored in the variable `values`. When using the dictionary return values, the `values` variable is a dictionary. - - 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. - - import PySimpleGUI as sg - window = sg.Window('Simple data entry window') - 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()] - ] - - button, values = window.Layout(layout).Read() - - sg.Popup(button, 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'] - -You will find the key field used quite heavily in most PySimpleGUI windows unless the window is very simple. - -### Button Return Values - -The button value from a Read call will be one of 3 values: -1. The Button's text -2. The Button's key -3. None - -If a button has a key set for it when it's created, then that key will be returned. If no key is set, then the button text is returned. If no button was clicked, but the window returned anyway, the button value is None. - -None is returned when the user clicks the X to close a window. - -If your window has an event loop where it is read over and over, remember to give your user an "out". You should always check for a None value and it's a good practice to provide an Exit button of some kind. Thus design patterns often resemble this Event Loop: - - while True: - button, values = window.Read() - if button is None or button == 'Quit': - break - -## 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. + + + 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. + + import PySimpleGUI as sg + window = sg.Window('Simple data entry window') + 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()] + ] + + event, values = window.Layout(layout).Read() + + 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. + +Another convention you'll see in some of the demo programs is keys being named with an underscore 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_'` + + + +### Button Event Return Values + +The button value from a Read call will be one of 3 values: +1. The Button's text +2. The Button's key + +If a button has a key set for it when it's created, then that key will be returned. If no key is set, then the button text is returned. If no button was clicked, but the window returned anyway, the button value is None. + +None is returned when the user clicks the X to close a window. + +If your window has an event loop where it is read over and over, remember to give your user an "out". You should always check for a None value and it's a good practice to provide an Exit button of some kind. Thus design patterns often resemble this Event Loop: + + while True: + event, values = window.Read() + if event is None or event == 'Quit': + break + +## 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 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. - -Let's take a Pi demo program as an example. This program shows a GUI window, gets button presses, and uses them to control some LEDs. It loops, reading user input and doing something with it. - -This little program has a typical 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. + +Let's take a Pi demo program as an example. This program shows a GUI window, gets button presses, and uses them to control some LEDs. It loops, reading user input and doing something with it. + +This little program has a typical Event Loop + ![readme example](https://user-images.githubusercontent.com/13696193/46566965-f4d65f80-c8f6-11e8-91a3-8cebad0cba90.jpg) - - - + + + ```python -import PySimpleGUI as sg - -layout = [[sg.Text('Click read to read the input value')], - [sg.Input()], - [sg.RButton('Read'), sg.Exit()]] - -window = sg.Window('Persistent GUI Window').Layout(layout) - -while True: - button, values = window.Read() - if button is None or button == 'Exit': - break - print(button, values) +import PySimpleGUI as sg + +layout = [[sg.Text('Click read to read the input value')], + [sg.Input()], + [sg.RButton('Read'), sg.Exit()]] + +window = sg.Window('Persistent GUI Window').Layout(layout) + +while True: + event, values = window.Read() + if event is None or event == 'Exit': + break + print(event, values) ``` - -In the Event Loop we are reading the window and then doing a series of button compares to determine what to do based on the button that was clicks (value of `button` variable) - + +In the Event Loop we are reading the window and then doing a series of button compares to determine what to do based on the button that was clicks (value of `button` variable) + The way buttons are presented to the caller in PySimpleGUI is ***not*** how *most* GUI frameworks handle button clicks. Most GUI frameworks, including tkinter, use ***callback*** functions, a function you define would be called when a button is clicked. This requires you to write asynchronous code, a concept beginners often stumble on and one that presents a barrier. - -There is a more communications that have to happen between parts of your program when using callbacks. Callbacks break apart your program's logic apart and scatter it. One of the larger hurdles for beginners to GUI programming are these callback functions. - -PySimpleGUI was specifically designed in a way so that callbacks would not be required. There is no coordination between one function and another required. You simply read your button click and take appropriate action at the same location in the code as when you read the button value. - -Whether or not this is a "proper" design for GUI programs can be debated. It's not a terrible trade-off to run your own event loop and having a functioning GUI application versus one that maybe never gets written because callback functions were too much to grasp. - - --- - -## All Widgets / Elements - + +There is a more communications that have to happen between parts of your program when using callbacks. Callbacks break apart your program's logic apart and scatter it. One of the larger hurdles for beginners to GUI programming are these callback functions. + +PySimpleGUI was specifically designed in a way so that callbacks would not be required. There is no coordination between one function and another required. You simply read your button click and take appropriate action at the same location in the code as when you read the button value. + +Whether or not this is a "proper" design for GUI programs can be debated. It's not a terrible trade-off to run your own event loop and having a functioning GUI application versus one that maybe never gets written because callback functions were too much to grasp. + + --- + +## All Widgets / Elements + This code utilizes many of the common Elements. It does not include Tabs/Tab Groups. - - import PySimpleGUI as sg - - sg.ChangeLookAndFeel('GreenTan') - - # ------ Menu Definition ------ # - menu_def = [['File', ['Open', 'Save', 'Exit', 'Properties']], - ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], - ['Help', 'About...'], ] - - # ------ Column Definition ------ # - column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]] - - layout = [ - [sg.Menu(menu_def, tearoff=True)], - [sg.Text('All graphic widgets in one window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)], - [sg.Text('Here is some text.... and a place to enter text')], - [sg.InputText('This is my text')], - [sg.Frame(layout=[ - [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)], - [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')], - [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)), - sg.Multiline(default_text='A second multi-line', size=(35, 3))], - [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)), - sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)], - [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))], - [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)), - sg.Frame('Labelled Group',[[ - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25), - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75), - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10), - sg.Column(column1, background_color='#F7F3EC')]])], - [sg.Text('_' * 80)], - [sg.Text('Choose A Folder', size=(35, 1))], - [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'), - sg.InputText('Default Folder'), sg.FolderBrowse()], - [sg.Submit(tooltip='Click to submit this window'), sg.Cancel()] - ] - - - window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout) - - button, values = window.Read() - - sg.Popup('Title', - 'The results of the window.', - 'The button clicked was "{}"'.format(button), - 'The values are', values) - -This is a somewhat complex window with quite a bit of custom sizing to make things line up well. This is code you only have to write once. When looking at the code, remember that what you're seeing is a list of lists. Each row contains a list of Graphical Elements that are used to create the window. - - ![everything bagel](https://user-images.githubusercontent.com/13696193/45914128-87163800-be0e-11e8-9a83-7ee5960e88b9.jpg) - -Clicking the Submit button caused the window call to return. The call to Popup resulted in this window. - -![everything bagel reseults](https://user-images.githubusercontent.com/13696193/45914129-87aece80-be0e-11e8-8aae-9a483a9ad4a6.jpg) - - +```python + import PySimpleGUI as sg + + sg.ChangeLookAndFeel('GreenTan') + + # ------ Menu Definition ------ # + menu_def = [['File', ['Open', 'Save', 'Exit', 'Properties']], + ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], + ['Help', 'About...'], ] + + # ------ Column Definition ------ # + column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]] + + layout = [ + [sg.Menu(menu_def, tearoff=True)], + [sg.Text('All graphic widgets in one window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)], + [sg.Text('Here is some text.... and a place to enter text')], + [sg.InputText('This is my text')], + [sg.Frame(layout=[ + [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)], + [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')], + [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)), + sg.Multiline(default_text='A second multi-line', size=(35, 3))], + [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)), + sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)], + [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))], + [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)), + sg.Frame('Labelled Group',[[ + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25), + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75), + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10), + sg.Column(column1, background_color='#F7F3EC')]])], + [sg.Text('_' * 80)], + [sg.Text('Choose A Folder', size=(35, 1))], + [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'), + sg.InputText('Default Folder'), sg.FolderBrowse()], + [sg.Submit(tooltip='Click to submit this window'), sg.Cancel()] + ] + + + window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout) + + event, values = window.Read() + + sg.Popup('Title', + 'The results of the window.', + 'The button clicked was "{}"'.format(event), + 'The values are', values) +``` +This is a somewhat complex window with quite a bit of custom sizing to make things line up well. This is code you only have to write once. When looking at the code, remember that what you're seeing is a list of lists. Each row contains a list of Graphical Elements that are used to create the window. + + ![everything bagel](https://user-images.githubusercontent.com/13696193/45914128-87163800-be0e-11e8-9a83-7ee5960e88b9.jpg) + +Clicking the Submit button caused the window call to return. The call to Popup resulted in this window. + +![everything bagel reseults](https://user-images.githubusercontent.com/13696193/45914129-87aece80-be0e-11e8-8aae-9a483a9ad4a6.jpg) + + **`Note, button value can be None`**. The value for `button` will be the text that is displayed on the button element when it was created. If the user closed the window using something other than a button, then `button` will be `None`. It is ***vitally*** ***important*** that your code contain the proper checks for None. Always give your users a way out of the window. Otherwise you'll end up with windows that never properly close. - -You can see in the results Popup window that the values returned are a list. Each input field in the window generates one item in the return values list. All input fields return a `string` except for Check Boxes and Radio Buttons. These return `bool`. - ---- -# 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 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 can see in the results Popup window that the values returned are a list. Each input field in the window generates one item in the return values list. All input fields return a `string` except for Check Boxes and Radio Buttons. These return `bool`. + +--- +# 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 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. The call to look for that will show you non-blocking windows are calls to `ReadNonBlocking()`. You can read more about Async windows at the end of this document. - -# Window Object - Beginning a window -The first step is to create the window object using the desired window customization. - - with Window('Everything bagel', auto_size_text=True, default_element_size=(30,1)) as window: - -This is the definition of the Window object: - - def Window(title, - default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), - default_button_element_size = (None, None), - auto_size_text=None, - auto_size_buttons=None, - location=(None, None), - font=None, - button_color=None,Font=None, - progress_bar_color=(None,None), - background_color=None - border_depth=None, - auto_close=False, - auto_close_duration=DEFAULT_AUTOCLOSE_TIME, - icon=DEFAULT_WINDOW_ICON, - force_toplevel=False - return_keyboard_events=False, - use_default_focus=True, - text_justification=None, - no_titlebar=False, - grab_anywhere=False - keep_on_top=False): - - -Parameter Descriptions. You will find these same parameters specified for each `Element` and some of them in `Row` specifications. The `Element` specified value will take precedence over the `Row` and `window` values. - - default_element_size - Size of elements in window in characters (width, height) - default_button_element_size - Size of buttons on this window - auto_size_text - Bool. True if elements should size themselves according to contents. Defaults to True - auto_size_buttons - Bool. True if button elements should size themselves according to their text label - location - (x,y) Location to place window in pixels - font - Font name and size for elements of the window - button_color - Default color for buttons (foreground, background). Can be text or hex - progress_bar_color - Foreground and background colors for progress bars - background_color - Color of the window background - border_depth - Amount of 'bezel' to put on input boxes, buttons, etc. - auto_close - Bool. If True window will autoclose - auto_close_duration - Duration in seconds before window closes - icon - .ICO file that will appear on the Task Bar and end of Title Bar - force_top_level - Bool. If set causes a tk.Tk window to be used as primary window rather than tk.TopLevel. Used to get around Matplotlib problem - return_keyboard_events - if True key presses are returned as buttons - use_default_focus - if True and no focus set, then automatically set a focus - text_justification - Justification to use for Text Elements in this window - no_titlebar - Create window without a titlebar - grab_anywhere - Grab any location on the window to move the window - keep_on_top - if True then window will always stop on top of other windows on the screen. Great for floating toolbars. - - -### 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. - -### Sizes -Note several variables that deal with "size". Element sizes are measured in characters. A Text Element with a size of 20,1 has a size of 20 characters wide by 1 character tall. - -The default Element size for PySimpleGUI is `(45,1)`. - -Sizes can be set at the element level, or in this case, the size variables apply to all elements in the window. Setting `size=(20,1)` in the window creation call will set all elements in the window to that size. - -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. - -### Window Methods (things you can do with a Window object) - -There are a few methods (functions) that you will see in this document that act on Windows. The ones you will primarily be calling are: - - window.Layout(layout) - Turns your definition of the Window into Window - window.Finalize() - creates the tkinter objects for the Window. Normally you do not call this - window.Read() - Read the Windows values and get the button / key that caused the Read to return - window.ReadNonBlocking() - Same as Read but will return right away - window.Refresh() - Use if updating elements and want to show the updates prior to the nex Read - window.Fill(values_dict) - Fill each Element with entry from the dictionary passed in - window.SaveToDisk(filename) - Save the Window's values to disk - window.LoadFromDisk(filename) - Load the Window's values from disk - window.CloseNonBlocking() - When done, for good, reading a non-blocking window - window.Disable() - Use to disable the window inpurt when opening another window on top of the primnary Window - window.Enable() - Re-enable a Disabled window - window.FindElement(key) - Returns the element that has a matching key value - -## Window Methods +# Window Object - Beginning a window +The first step is to create the window object using the desired window customization. + + with Window('Everything bagel', auto_size_text=True, default_element_size=(30,1)) as window: + +This is the definition of the Window object: + + def Window(title, + default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), + default_button_element_size = (None, None), + auto_size_text=None, + auto_size_buttons=None, + location=(None, None), + font=None, + button_color=None,Font=None, + progress_bar_color=(None,None), + background_color=None + border_depth=None, + auto_close=False, + auto_close_duration=DEFAULT_AUTOCLOSE_TIME, + icon=DEFAULT_WINDOW_ICON, + force_toplevel=False + return_keyboard_events=False, + use_default_focus=True, + text_justification=None, + no_titlebar=False, + grab_anywhere=False + keep_on_top=False): + + + +Parameter Descriptions. You will find these same parameters specified for each `Element` and some of them in `Row` specifications. The `Element` specified value will take precedence over the `Row` and `window` values. + + default_element_size - Size of elements in window in characters (width, height) + default_button_element_size - Size of buttons on this window + auto_size_text - Bool. True if elements should size themselves according to contents. Defaults to True + auto_size_buttons - Bool. True if button elements should size themselves according to their text label + location - (x,y) Location to place window in pixels + font - Font name and size for elements of the window + button_color - Default color for buttons (foreground, background). Can be text or hex + progress_bar_color - Foreground and background colors for progress bars + background_color - Color of the window background + border_depth - Amount of 'bezel' to put on input boxes, buttons, etc. + auto_close - Bool. If True window will autoclose + auto_close_duration - Duration in seconds before window closes + icon - .ICO file that will appear on the Task Bar and end of Title Bar + force_top_level - Bool. If set causes a tk.Tk window to be used as primary window rather than tk.TopLevel. Used to get around Matplotlib problem + return_keyboard_events - if True key presses are returned as buttons + use_default_focus - if True and no focus set, then automatically set a focus + text_justification - Justification to use for Text Elements in this window + no_titlebar - Create window without a titlebar + grab_anywhere - Grab any location on the window to move the window + keep_on_top - if True then window will always stop on top of other windows on the screen. Great for floating toolbars. + + +### 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. + +### Sizes +Note several variables that deal with "size". Element sizes are measured in characters. A Text Element with a size of 20,1 has a size of 20 characters wide by 1 character tall. + +The default Element size for PySimpleGUI is `(45,1)`. + +Sizes can be set at the element level, or in this case, the size variables apply to all elements in the window. Setting `size=(20,1)` in the window creation call will set all elements in the window to that size. + +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. + +### Window Methods (things you can do with a Window object) + +There are a few methods (functions) that you will see in this document that act on Windows. The ones you will primarily be calling are: + + window.Layout(layout) - Turns your definition of the Window into Window + window.Finalize() - creates the tkinter objects for the Window. Normally you do not call this + window.Read() - Read the Windows values and get the button / key that caused the Read to return + window.ReadNonBlocking() - Same as Read but will return right away + window.Refresh() - Use if updating elements and want to show the updates prior to the nex Read + window.Fill(values_dict) - Fill each Element with entry from the dictionary passed in + window.SaveToDisk(filename) - Save the Window's values to disk + window.LoadFromDisk(filename) - Load the Window's values from disk + window.CloseNonBlocking() - When done, for good, reading a non-blocking window + window.Disable() - Use to disable the window inpurt when opening another window on top of the primnary Window + window.Enable() - Re-enable a Disabled window + window.FindElement(key) - Returns the element that has a matching key value + +## Window Methods There are a number of operations you can do on a window after you've created the window. You call these after creating your Windows object. @@ -1255,7 +1307,7 @@ Call to force a window to go through the final stages of initialization. This w #### Read() Read the Window's input values and button clicks in a blocking-fashion -Returns button, values +Returns event, values #### ReadNonBlocking() @@ -1265,7 +1317,7 @@ Read the Window's input values and button clicks but without blocking. It will Cause changes to the window to be displayed on the screen. Normally not needed unless the changes are immediately required or if it's going to be a while before another call to Read. #### Fill(values_dict) -Populates the windows fields with the values shown in the dictionary. +Populates the windows fields with the values shown in the dictionary. #### FindElement(key) @@ -1313,44 +1365,44 @@ Makes a window reappear that was previously made to disappear using Disappear() - -# Elements -"Elements" are the building blocks used to create windows. Some GUI APIs use the term "Widget" to describe these graphic elements. - - Text - Single Line Input - Buttons including these types: - File Browse - Folder Browse - Calendar picker - Date Chooser - Read window - Close window - Realtime - Checkboxes - Radio Buttons - Listbox - Slider - Multi-line Text Input - Scroll-able Output - Progress Bar - Option Menu - Menu - Frame - Column - Graph - Image - Table + +# Elements +"Elements" are the building blocks used to create windows. Some GUI APIs use the term "Widget" to describe these graphic elements. + + Text + Single Line Input + Buttons including these types: + File Browse + Folder Browse + Calendar picker + Date Chooser + Read window + Close window + Realtime + Checkboxes + Radio Buttons + Listbox + Slider + Multi-line Text Input + Scroll-able Output + Progress Bar + Option Menu + Menu + Frame + Column + Graph + Image + Table Tree - Tab, TabGroup - Async/Non-Blocking Windows - Tabbed windows - Persistent Windows - Redirect Python Output/Errors to scrolling Window - "Higher level" APIs (e.g. MessageBox, YesNobox, ...) - -## Common Element Parameters -Some parameters that you will see on almost all Elements are: + Tab, TabGroup + Async/Non-Blocking Windows + Tabbed windows + Persistent Windows + Redirect Python Output/Errors to scrolling Window + "Higher level" APIs (e.g. MessageBox, YesNobox, ...) + +## Common Element Parameters +Some parameters that you will see on almost all Elements are: - key - Used with window.FindElement and with return values - tooltip - Hover your mouse over the elemnt and you'll get a popup with this text @@ -1359,14 +1411,14 @@ Some parameters that you will see on almost all Elements are: - colors - Color name or #RRGGBB string - pad - Amount of padding to put around element - + #### Tooltip -Tooltips are text boxes that popup next to an element if you hold your mouse over the top of it. If you want to be extra kind to your window's user, then you can create tooltips for them by setting the parameter `tooltip` to some text string. You will need to supply your own line breaks / text wrapping. If you don't want to manually add them, then take a look at the standard library package `textwrap`. - -Tooltips are one of those "polish" items that really dress-up a GUI and show's a level of sophistication. Go ahead, impress people, throw some tooltips into your GUI. - +Tooltips are text boxes that popup next to an element if you hold your mouse over the top of it. If you want to be extra kind to your window's user, then you can create tooltips for them by setting the parameter `tooltip` to some text string. You will need to supply your own line breaks / text wrapping. If you don't want to manually add them, then take a look at the standard library package `textwrap`. + +Tooltips are one of those "polish" items that really dress-up a GUI and show's a level of sophistication. Go ahead, impress people, throw some tooltips into your GUI. + #### Size -Specifies the amount of room reserved for the Element. For elements that are character based, such a Text, it is (# characters, # rows). Sometimes it is a pixel measurement such as the Image element. And sometimes a mix like on the Slider element (characters long by pixels wide). +Specifies the amount of room reserved for the Element. For elements that are character based, such a Text, it is (# characters, # rows). Sometimes it is a pixel measurement such as the Image element. And sometimes a mix like on the Slider element (characters long by pixels wide). #### Colors A string representing color. Anytime colors are involved, you can specify the tkinter color name such as 'lightblue' or an RGB hex value '#RRGGBB'. For buttons, the color parameter is a tuple (text color, background color) @@ -1401,80 +1453,80 @@ If you are going to do anything beyond the basic stuff with your GUI, then you n Keys are a way for you to "tag" an Element with a value that will be used to identify that element. After you put a key in an element's definition, the values returned from Read will use that key to tell you the value. For example, if you have an input field: Input(key='mykey') And your read looks like this: -button, values = Read() +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 below on Updates to understand that usage. - -### Output Elements -Building a window is simply making lists of Elements. Each list is a row in the overall GUI dialog box. The definition looks something like this: - - layout = [ [row 1 element, row 1 element], - [row 2 element, row 2 element, row 2 element] ] -The code is a crude representation of the GUI, laid out in text. - -## Text Element - - layout = [[sg.Text('This is what a Text Element looks like')]] - - ![simple text](https://user-images.githubusercontent.com/13696193/44959877-e9d97b00-aec3-11e8-9d24-b4405ee4a148.jpg) - - -The most basic element is the Text element. It simply displays text. Many of the 'options' that can be set for a Text element are shared by other elements. - - Text(text - size=(None, None) - auto_size_text=None - click_submits=None - relief=None - font=None - text_color=None - background_color=None - justification=None - pad=None - key=None - tooltip=None) - -. - - Text - The text that's displayed - size - Element's size - click_submits - if clicked will cause a read call to return they key value as the button - relief - relief to use around the text - auto_size_text - Bool. Change width to match size of text - font - Font name and size to use - text_color - text color - background_color - background color - justification - Justification for the text. String - 'left', 'right', 'center' - pad - (x,y) amount of padding in pixels to use around element when packing - key - used to identify element. This value will return as button if click_submits True - tooltip - string representing tooltip - -Some commonly used elements have 'shorthand' versions of the functions to make the code more compact. The functions `T` and `Txt` are the same as calling `Text`. - -**Fonts** in PySimpleGUI are always in this format: - - (font_name, point_size) - -The default font setting is - - ("Helvetica", 10) - + +### Output Elements +Building a window is simply making lists of Elements. Each list is a row in the overall GUI dialog box. The definition looks something like this: + + layout = [ [row 1 element, row 1 element], + [row 2 element, row 2 element, row 2 element] ] +The code is a crude representation of the GUI, laid out in text. + +## Text Element + + layout = [[sg.Text('This is what a Text Element looks like')]] + + ![simple text](https://user-images.githubusercontent.com/13696193/44959877-e9d97b00-aec3-11e8-9d24-b4405ee4a148.jpg) + + +The most basic element is the Text element. It simply displays text. Many of the 'options' that can be set for a Text element are shared by other elements. + + Text(text + size=(None, None) + auto_size_text=None + click_submits=None + relief=None + font=None + text_color=None + background_color=None + justification=None + pad=None + key=None + tooltip=None) + +. + + Text - The text that's displayed + size - Element's size + click_submits - if clicked will cause a read call to return they key value as the button + relief - relief to use around the text + auto_size_text - Bool. Change width to match size of text + font - Font name and size to use + text_color - text color + background_color - background color + justification - Justification for the text. String - 'left', 'right', 'center' + pad - (x,y) amount of padding in pixels to use around element when packing + key - used to identify element. This value will return as button if click_submits True + tooltip - string representing tooltip + +Some commonly used elements have 'shorthand' versions of the functions to make the code more compact. The functions `T` and `Txt` are the same as calling `Text`. + +**Fonts** in PySimpleGUI are always in this format: + + (font_name, point_size) + +The default font setting is + + ("Helvetica", 10) + **Color** in PySimpleGUI are in one of two formats - color name or RGB value. - - Individual colors are specified using either the color names as defined in tkinter or an RGB string of this format: - - "#RRGGBB" - -**auto_size_text** -A `True` value for `auto_size_text`, when placed on Text Elements, indicates that the width of the Element should be shrunk do the width of the text. The default setting is True. - - -**Shortcut functions** -The shorthand functions for `Text` are `Txt` and `T` - -### Text Methods + + Individual colors are specified using either the color names as defined in tkinter or an RGB string of this format: + + "#RRGGBB" + +**auto_size_text** +A `True` value for `auto_size_text`, when placed on Text Elements, indicates that the width of the Element should be shrunk do the width of the text. The default setting is True. + + +**Shortcut functions** +The shorthand functions for `Text` are `Txt` and `T` + +### Text Methods #### Update ```python @@ -1486,14 +1538,14 @@ text_color - text color to display font - font to use to display -## Multiline Text Element - - layout = [[sg.Multiline('This is what a Multi-line Text Element looks like', size=(45,5))]] - -![multiline](https://user-images.githubusercontent.com/13696193/44959853-b139a180-aec3-11e8-972f-f52188510c88.jpg) - -This Element doubles as both an input and output Element. - +## Multiline Text Element + + layout = [[sg.Multiline('This is what a Multi-line Text Element looks like', size=(45,5))]] + +![multiline](https://user-images.githubusercontent.com/13696193/44959853-b139a180-aec3-11e8-972f-f52188510c88.jpg) + +This Element doubles as both an input and output Element. + Multiline(default_text='', enter_submits = False, disabled=False, @@ -1507,13 +1559,13 @@ This Element doubles as both an input and output Element. focus=False, pad=None, tooltip=None) -. - - default_text - Text to display in the text box - enter_submits - Bool. If True, pressing Enter key submits window - size - Element's size - auto_size_text - Bool. Change width to match size of text - +. + + default_text - Text to display in the text box + enter_submits - Bool. If True, pressing Enter key submits window + size - Element's size + auto_size_text - Bool. Change width to match size of text + ### Multiline Methods ```python Update(value=None, disabled=None, append=False): @@ -1522,33 +1574,33 @@ value - string to set the text field to disabled - set to True to disable the element append - rather than replacing the current text with new text, add the new text onto the end -## Output Element -Output re-routes `Stdout` to a scrolled text box. It's used with Async windows. More on this later. - - window.AddRow(gg.Output(size=(100,20))) - -![output](https://user-images.githubusercontent.com/13696193/44959863-b72f8280-aec3-11e8-8caa-7bc743149953.jpg) - - Output(size=(None, None)) -. - - size - Size of element (width, height) in characters - -## Input Elements - These make up the majority of the window definition. Optional variables at the Element level override the window level values (e.g. `size` is specified in the Element). All input Elements create an entry in the list of return values. A Text Input Element creates a string in the list of items returned. - -## Text Input Element - - layout = [[sg.InputText('Default text')]] - -![inputtext 2](https://user-images.githubusercontent.com/13696193/44959861-b5fe5580-aec3-11e8-8040-53ec241b5079.jpg) - - +## Output Element +Output re-routes `Stdout` to a scrolled text box. It's used with Async windows. More on this later. + + window.AddRow(gg.Output(size=(100,20))) + +![output](https://user-images.githubusercontent.com/13696193/44959863-b72f8280-aec3-11e8-8caa-7bc743149953.jpg) + + Output(size=(None, None)) +. + + size - Size of element (width, height) in characters + +## Input Elements + These make up the majority of the window definition. Optional variables at the Element level override the window level values (e.g. `size` is specified in the Element). All input Elements create an entry in the list of return values. A Text Input Element creates a string in the list of items returned. + +## Text Input Element + + layout = [[sg.InputText('Default text')]] + +![inputtext 2](https://user-images.githubusercontent.com/13696193/44959861-b5fe5580-aec3-11e8-8040-53ec241b5079.jpg) + + def InputText(default_text ='', size=(None, None), - disabled=False, + disabled=False, auto_size_text=None, - password_char='', + password_char='', justification=None, background_color=None, text_color=None, @@ -1557,30 +1609,30 @@ Output re-routes `Stdout` to a scrolled text box. It's used with Async windows. do_not_clear=False, key=None, focus=False, - pad=None): + pad=None): ''' - -. - - default_text - Text initially shown in the input box - size - (width, height) of element in characters - auto_size_text- Bool. True is element should be sized to fit text + +. + + default_text - Text initially shown in the input box + size - (width, height) of element in characters + auto_size_text- Bool. True is element should be sized to fit text disabled - Bool If True the input is disabled - password_char - Character that will be used to replace each entered character. Setting to a value indicates this field is a password entry field - background_color - color to use for the input field background - text_color - color to use for the typed text - do_not_clear - Bool. Normally windows clear when read, turn off clearing with this flag. - key = Dictionary key to use for return values - focus = Bool. True if this field should capture the focus (moves cursor to this field) - - There are two methods that can be called: - - InputText.Update(new_Value) - sets the input value - Input.Text(Get() - returns the current value of the field. - - -Shorthand functions that are equivalent to `InputText` are `Input` and `In` - + password_char - Character that will be used to replace each entered character. Setting to a value indicates this field is a password entry field + background_color - color to use for the input field background + text_color - color to use for the typed text + do_not_clear - Bool. Normally windows clear when read, turn off clearing with this flag. + key = Dictionary key to use for return values + focus = Bool. True if this field should capture the focus (moves cursor to this field) + + There are two methods that can be called: + + InputText.Update(new_Value) - sets the input value + Input.Text(Get() - returns the current value of the field. + + +Shorthand functions that are equivalent to `InputText` are `Input` and `In` + ### TextInput Methods ```python @@ -1592,40 +1644,40 @@ value - new value to display in field disabled - if True will disable the element Get - Returns the current value for the element (you can get also from a call to Read) - - -## Combo Element -Also known as a drop-down list. Only required parameter is the list of choices. The return value is a string matching what's visible on the GUI. - - layout = [[sg.InputCombo(['choice 1', 'choice 2'])]] - -![combobox](https://user-images.githubusercontent.com/13696193/44959860-b565bf00-aec3-11e8-82fe-dbe41252458b.jpg) - - InputCombo(values, , - default_value=None - size=(None, None) - auto_size_text=None - background_color=None - text_color=None - change_submits=False - disabled=False - key=None - pad=None - tooltip=None -. - - values - Choices to be displayed. List of strings - default_value - which value should be initially chosen - size - (width, height) of element in characters - auto_size_text - Bool. True if size should fit the text length - background_color - color to use for the input field background - text_color - color to use for the typed text - change_submits - Bool. If set causes Read to immediately return if the selected value changes - disabled - Bool. If set will disable changes - key - Dictionary key to use for return values - pad - (x,y) Amount of padding to put around element in pixels - tooltip - Text string. If set, hovering over field will popup the text - + + +## Combo Element +Also known as a drop-down list. Only required parameter is the list of choices. The return value is a string matching what's visible on the GUI. + + layout = [[sg.InputCombo(['choice 1', 'choice 2'])]] + +![combobox](https://user-images.githubusercontent.com/13696193/44959860-b565bf00-aec3-11e8-82fe-dbe41252458b.jpg) + + InputCombo(values, , + default_value=None + size=(None, None) + auto_size_text=None + background_color=None + text_color=None + change_submits=False + disabled=False + key=None + pad=None + tooltip=None +. + + values - Choices to be displayed. List of strings + default_value - which value should be initially chosen + size - (width, height) of element in characters + auto_size_text - Bool. True if size should fit the text length + background_color - color to use for the input field background + text_color - color to use for the typed text + change_submits - Bool. If set causes Read to immediately return if the selected value changes + disabled - Bool. If set will disable changes + key - Dictionary key to use for return values + pad - (x,y) Amount of padding to put around element in pixels + tooltip - Text string. If set, hovering over field will popup the text + Shortcut functions - Combo, DropDown, Drop ### Combo Methods @@ -1637,61 +1689,61 @@ values - change list of choices set_to_index - change selection to a particular choice disable - if True will disable element -## Listbox Element -The standard listbox like you'll find in most GUIs. Note that the return values from this element will be a ***list of results, not a single result***. This is because the user can select more than 1 item from the list (if you set the right mode). - - layout = [[sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6))]] - -![listbox 2](https://user-images.githubusercontent.com/13696193/44959859-b4cd2880-aec3-11e8-881c-1e369d5c6337.jpg) - - - Listbox(values - default_values=None - select_mode=None - change_submits=False - bind_return_key=False - size=(None, None) +## Listbox Element +The standard listbox like you'll find in most GUIs. Note that the return values from this element will be a ***list of results, not a single result***. This is because the user can select more than 1 item from the list (if you set the right mode). + + layout = [[sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6))]] + +![listbox 2](https://user-images.githubusercontent.com/13696193/44959859-b4cd2880-aec3-11e8-881c-1e369d5c6337.jpg) + + + Listbox(values + default_values=None + select_mode=None + change_submits=False + bind_return_key=False + size=(None, None) disabled = False, - auto_size_text=None - font=None - background_color=None - text_color=None - key=None - pad=None - tooltip=None): - -. - - values - Choices to be displayed. List of strings - select_mode - Defines how to list is to operate. - Choices include constants or strings: - Constants version: - LISTBOX_SELECT_MODE_BROWSE - LISTBOX_SELECT_MODE_EXTENDED - LISTBOX_SELECT_MODE_MULTIPLE - LISTBOX_SELECT_MODE_SINGLE - the default - Strings version: - 'browse' - 'extended' - 'multiple' - 'single' - change_submits - if True, the window read will return with a button value of '' - bind_return_key - if the focus is on the listbox and the user presses return key, or if the user double clicks an item, then the read will return - size - (width, height) of element in characters + auto_size_text=None + font=None + background_color=None + text_color=None + key=None + pad=None + tooltip=None): + +. + + values - Choices to be displayed. List of strings + select_mode - Defines how to list is to operate. + Choices include constants or strings: + Constants version: + LISTBOX_SELECT_MODE_BROWSE + LISTBOX_SELECT_MODE_EXTENDED + LISTBOX_SELECT_MODE_MULTIPLE + LISTBOX_SELECT_MODE_SINGLE - the default + Strings version: + 'browse' + 'extended' + 'multiple' + 'single' + change_submits - if True, the window read will return with a button value of '' + bind_return_key - if the focus is on the listbox and the user presses return key, or if the user double clicks an item, then the read will return + size - (width, height) of element in characters disapled - Bool. If True element is disabled - auto_size_text - Bool. True if size should fit the text length - background_color - color to use for the input field background - font - font to use for items in list - text_color - color to use for the typed text - key - Dictionary key to use for return values and to find element - pad - amount of padding to use when packing - tooltip - tooltip text - -The `select_mode` option can be a string or a constant value defined as a variable. Generally speaking strings are used for these kinds of options. - -ListBoxes can cause a window to return from a Read call. If the flag change_submits is set, then when a user makes a selection, the Read immediately returns. -Another way ListBoxes can cause Reads to return is if the flag bind_return_key is set. If True, then if the user presses the return key while an entry is selected, then the Read returns. Also, if this flag is set, if the user double-clicks an entry it will return from the Read. - + auto_size_text - Bool. True if size should fit the text length + background_color - color to use for the input field background + font - font to use for items in list + text_color - color to use for the typed text + key - Dictionary key to use for return values and to find element + pad - amount of padding to use when packing + tooltip - tooltip text + +The `select_mode` option can be a string or a constant value defined as a variable. Generally speaking strings are used for these kinds of options. + +ListBoxes can cause a window to return from a Read call. If the flag change_submits is set, then when a user makes a selection, the Read immediately returns. +Another way ListBoxes can cause Reads to return is if the flag bind_return_key is set. If True, then if the user presses the return key while an entry is selected, then the Read returns. Also, if this flag is set, if the user double-clicks an entry it will return from the Read. + ### Listbox Methods ```python Update(values=None, disabled=None) @@ -1708,53 +1760,53 @@ SetValue - Sets selection to one or more values GetListValues - Return the list of values to choose from -## Slider Element - -Sliders have a couple of slider-specific settings as well as appearance settings. Examples include the `orientation` and `range` settings. - - layout = [[sg.Slider(range=(1,500), default_value=222, size=(20,15), orientation='horizontal', font=('Helvetica', 12))]] - -![slider](https://user-images.githubusercontent.com/13696193/44959858-b4349200-aec3-11e8-9e25-c0fcf025d19e.jpg) - - Slider(range=(None,None), - default_value=None, - resolution=None, - orientation=None, - border_width=None, - relief=None, - change_submits=False, - disabled=False, - size=(None, None), - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None) +## Slider Element -. - - range - (min, max) slider's range - default_value - default setting (within range) +Sliders have a couple of slider-specific settings as well as appearance settings. Examples include the `orientation` and `range` settings. + + layout = [[sg.Slider(range=(1,500), default_value=222, size=(20,15), orientation='horizontal', font=('Helvetica', 12))]] + +![slider](https://user-images.githubusercontent.com/13696193/44959858-b4349200-aec3-11e8-9e25-c0fcf025d19e.jpg) + + Slider(range=(None,None), + default_value=None, + resolution=None, + orientation=None, + border_width=None, + relief=None, + change_submits=False, + disabled=False, + size=(None, None), + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None) + +. + + range - (min, max) slider's range + default_value - default setting (within range) resolution - how much each 'tick' should represent. Default = 1 - orientation - 'horizontal' or 'vertical' ('h' or 'v' work) - border_width - how deep the widget looks - relief - relief style. Values are same as progress meter relief values. Can be a constant or a string: - RELIEF_RAISED= 'raised' - RELIEF_SUNKEN= 'sunken' - RELIEF_FLAT= 'flat' - RELIEF_RIDGE= 'ridge' - RELIEF_GROOVE= 'groove' - RELIEF_SOLID = 'solid' - size - (width, height) of element in characters + orientation - 'horizontal' or 'vertical' ('h' or 'v' work) + border_width - how deep the widget looks + relief - relief style. Values are same as progress meter relief values. Can be a constant or a string: + RELIEF_RAISED= 'raised' + RELIEF_SUNKEN= 'sunken' + RELIEF_FLAT= 'flat' + RELIEF_RIDGE= 'ridge' + RELIEF_GROOVE= 'groove' + RELIEF_SOLID = 'solid' + size - (width, height) of element in characters disabled - Bool If True slider is disabled - auto_size_text - Bool. True if size should fit the text - background_color - color to use for the input field background - text_color - color to use for the typed text - change_submits - causes window read to immediately return if the checkbox value changes - key- Dictionary key to use for return values + auto_size_text - Bool. True if size should fit the text + background_color - color to use for the input field background + text_color - color to use for the typed text + change_submits - causes window read to immediately return if the checkbox value changes + key- Dictionary key to use for return values tooltip - Tooltip to display when hovered over wlement - + ### Slider Methods ```python Update(self, value=None, range=(None, None), disabled=None): @@ -1764,42 +1816,42 @@ range - change range of valid values disabled - if True disables element -## Radio Button Element - -Creates one radio button that is assigned to a group of radio buttons. Only 1 of the buttons in the group can be selected at any one time. - - layout = [[sg.Radio('My first Radio!', "RADIO1", default=True), sg.Radio('My second radio!', "RADIO1")]] - -![radio](https://user-images.githubusercontent.com/13696193/44959857-b4349200-aec3-11e8-8e2d-e6a49ffbd0b6.jpg) - - Radio(text, - group_id, - default=False, - size=(None, None), +## Radio Button Element + +Creates one radio button that is assigned to a group of radio buttons. Only 1 of the buttons in the group can be selected at any one time. + + layout = [[sg.Radio('My first Radio!', "RADIO1", default=True), sg.Radio('My second radio!', "RADIO1")]] + +![radio](https://user-images.githubusercontent.com/13696193/44959857-b4349200-aec3-11e8-8e2d-e6a49ffbd0b6.jpg) + + Radio(text, + group_id, + default=False, + size=(None, None), disabled = False, - auto_size_text=None, - font=None, - background_color = None, - text_color = None, + auto_size_text=None, + font=None, + background_color = None, + text_color = None, key = None, pad = None, - tooltip = None) - -. - - text - Text to display next to button - group_id - Groups together multiple Radio Buttons. Can be any value - default - Bool. Initial state - size - (width, height) size of element in characters - auto_size_text - Bool. True if should size width to fit text - font - Font type and size for text display - background_color - color to use for the background - text_color - color to use for the text - key - Dictionary key to use for return values + tooltip = None) + +. + + text - Text to display next to button + group_id - Groups together multiple Radio Buttons. Can be any value + default - Bool. Initial state + size - (width, height) size of element in characters + auto_size_text - Bool. True if should size width to fit text + font - Font type and size for text display + background_color - color to use for the background + text_color - color to use for the text + key - Dictionary key to use for return values pad - padding around element tooltip - tooltip to show when mouse hovered over element - - + + ### Radio Button Methods ```python Update(value=None, disabled=None) @@ -1808,39 +1860,39 @@ value - bool - if True change to selected disabled - if True disables the element -## Checkbox Element -Checkbox elements are like Radio Button elements. They return a bool indicating whether or not they are checked. - - layout = [[sg.Checkbox('My first Checkbox!', default=True), sg.Checkbox('My second Checkbox!')]] - - -![checkbox](https://user-images.githubusercontent.com/13696193/44959906-6f5d2b00-aec4-11e8-9c8a-962c787f0286.jpg) - - - Checkbox(text, - default=False, - size=(None, None), - auto_size_text=None, - font=None, - background_color = None, - text_color = None, - change_submits = False +## Checkbox Element +Checkbox elements are like Radio Button elements. They return a bool indicating whether or not they are checked. + + layout = [[sg.Checkbox('My first Checkbox!', default=True), sg.Checkbox('My second Checkbox!')]] + + +![checkbox](https://user-images.githubusercontent.com/13696193/44959906-6f5d2b00-aec4-11e8-9c8a-962c787f0286.jpg) + + + Checkbox(text, + default=False, + size=(None, None), + auto_size_text=None, + font=None, + background_color = None, + text_color = None, + change_submits = False disabled = False, key = None, pad = None, - tooltip = None): -. - - text - Text to display next to checkbox - default- Bool + None. Initial state. True = Checked, False = unchecked, None = Not available (grayed out) - size - (width, height) size of element in characters - auto_size_text- Bool. True if should size width to fit text + tooltip = None): +. + + text - Text to display next to checkbox + default- Bool + None. Initial state. True = Checked, False = unchecked, None = Not available (grayed out) + size - (width, height) size of element in characters + auto_size_text- Bool. True if should size width to fit text disabled - Bool. If True element is disabled - font- Font type and size for text display - background_color - color to use for the background - text_color - color to use for the typed text - change_submits - causes window read to immediately return if the checkbox value changes - key = Dictionary key to use for return values + font- Font type and size for text display + background_color - color to use for the background + text_color - color to use for the typed text + change_submits - causes window read to immediately return if the checkbox value changes + key = Dictionary key to use for return values pad - Padding around element in window tooltip - text to show when mouse is hovered over element @@ -1857,45 +1909,45 @@ disabled - if True disables the element Get - returns current state - -## Spin Element - -An up/down spinner control. The valid values are passed in as a list. - - layout = [[sg.Spin([i for i in range(1,11)], initial_value=1), sg.Text('Volume level')]] - -![spinner](https://user-images.githubusercontent.com/13696193/44959855-b1d23800-aec3-11e8-9f51-afb2109879da.jpg) - - Spin(values, - intiial_value=None, - disabled = False, - size=(None, None), + +## Spin Element + +An up/down spinner control. The valid values are passed in as a list. + + layout = [[sg.Spin([i for i in range(1,11)], initial_value=1), sg.Text('Volume level')]] + +![spinner](https://user-images.githubusercontent.com/13696193/44959855-b1d23800-aec3-11e8-9f51-afb2109879da.jpg) + + Spin(values, + intiial_value=None, + disabled = False, + size=(None, None), change_submits = False, - auto_size_text=None, - font=None, - background_color = None, - text_color = None, + auto_size_text=None, + font=None, + background_color = None, + text_color = None, key = None. pad = None, - tooltip = None): + tooltip = None): Parameter definitions - - values - List of valid values - initial_value - String with initial value - size - (width, height) size of element in characters - auto_size_text - Bool. True if should size width to fit text - font - Font type and size for text display + + values - List of valid values + initial_value - String with initial value + size - (width, height) size of element in characters + auto_size_text - Bool. True if should size width to fit text + font - Font type and size for text display disabled - Bool. If True element is disabled - background_color - color to use for the background - text_color - color to use for the typed text - change_submits - causes window read to immediately return if the spinner value changes - key = Dictionary key to use for return values + background_color - color to use for the background + text_color - color to use for the typed text + change_submits - causes window read to immediately return if the spinner value changes + key = Dictionary key to use for return values pad - padding around element in the window - tooltip - text to show when mouse hovered over element - + tooltip - text to show when mouse hovered over element + ### Spin Methods ```python Update(value=None, values=None, disabled=None) @@ -1908,16 +1960,16 @@ disabled - if True disables the element Images can be placed in your window provide they are in PNG, GIF, PPM/PGM format. JPGs cannot be shown because tkinter does not naively support JPGs. You can use the Python Imaging Library (PIL) package to convert your image to PNG prior to calling PySimpleGUI if your images are in JPG format. - Image(filename=None, - data=None, - background_color=None, - size=(None, None), - pad=None, + Image(filename=None, + data=None, + background_color=None, + size=(None, None), + pad=None, key=None, tooltip=None) - + Parameter definitions - + filename - file name if the image is in a file data - if image is in RAM (PIL format?) background_color - Color of background @@ -1936,279 +1988,279 @@ Like other Elements, the Image Element has an update method. Call Update if you Choose **either** a filename or in-ram data image to use to replace current image -## Button Element - -Buttons are the most important element of all! They cause the majority of the action to happen. After all, it's a button press that will get you out of a window, whether it be Submit or Cancel, one way or another a button is involved in all windows. The only exception is to this is when the user closes the window using the "X" in the upper corner which means no button was involved. - -The Types of buttons include: -* Folder Browse -* File Browse -* Files Browse -* File SaveAs -* File Save -* Close window (normal button) -* Read window -* Realtime -* Calendar Chooser -* Color Chooser - - - Close window - Normal buttons like Submit, Cancel, Yes, No, etc, are "Close window" buttons. They cause the input values to be read and then the window is ***closed***, returning the values to the caller. - -Folder Browse - When clicked a folder browse dialog box is opened. The results of the Folder Browse dialog box are written into one of the input fields of the window. - -File Browse - Same as the Folder Browse except rather than choosing a folder, a single file is chosen. - -Calendar Chooser - Opens a graphical calendar to select a date. - -Color Chooser - Opens a color chooser dialog - -Read window - This is a window button that will read a snapshot of all of the input fields, but does not close the window after it's clicked. - -Realtime - This is another async window button. Normal button clicks occur after a button's click is released. Realtime buttons report a click the entire time the button is held down. - -Most programs will use a combination of shortcut button calls (Submit, Cancel, etc), plain buttons that close the window, and ReadForm buttons that keep the window open but returns control back to the caller. - -Sometimes there are multiple names for the same function. This is simply to make the job of the programmer quicker and easier. - -The 3 primary windows of PySimpleGUI buttons and their names are: - - 1. `Button` = `SimpleButton` - 2. `ReadButton` = `RButton` = `ReadFormButton` (old style... use ReadButton instead) - 3. `RealtimeButton` - -You will find the long-form in the older programs. - -The most basic Button element call to use is `Button` - - Button(button_text='' - button_type=BUTTON_TYPE_CLOSES_WIN - target=(None, None) - tooltip=None - file_types=(("ALL Files", "*.*"),) - initial_folder=None +## Button Element + +Buttons are the most important element of all! They cause the majority of the action to happen. After all, it's a button press that will get you out of a window, whether it be Submit or Cancel, one way or another a button is involved in all windows. The only exception is to this is when the user closes the window using the "X" in the upper corner which means no button was involved. + +The Types of buttons include: +* Folder Browse +* File Browse +* Files Browse +* File SaveAs +* File Save +* Close window (normal button) +* Read window +* Realtime +* Calendar Chooser +* Color Chooser + + + Close window - Normal buttons like Submit, Cancel, Yes, No, etc, are "Close window" buttons. They cause the input values to be read and then the window is ***closed***, returning the values to the caller. + +Folder Browse - When clicked a folder browse dialog box is opened. The results of the Folder Browse dialog box are written into one of the input fields of the window. + +File Browse - Same as the Folder Browse except rather than choosing a folder, a single file is chosen. + +Calendar Chooser - Opens a graphical calendar to select a date. + +Color Chooser - Opens a color chooser dialog + +Read window - This is a window button that will read a snapshot of all of the input fields, but does not close the window after it's clicked. + +Realtime - This is another async window button. Normal button clicks occur after a button's click is released. Realtime buttons report a click the entire time the button is held down. + +Most programs will use a combination of shortcut button calls (Submit, Cancel, etc), plain buttons that close the window, and ReadForm buttons that keep the window open but returns control back to the caller. + +Sometimes there are multiple names for the same function. This is simply to make the job of the programmer quicker and easier. + +The 3 primary windows of PySimpleGUI buttons and their names are: + + 1. `Button` = `SimpleButton` + 2. `ReadButton` = `RButton` = `ReadFormButton` (old style... use ReadButton instead) + 3. `RealtimeButton` + +You will find the long-form in the older programs. + +The most basic Button element call to use is `Button` + + Button(button_text='' + button_type=BUTTON_TYPE_CLOSES_WIN + target=(None, None) + tooltip=None + file_types=(("ALL Files", "*.*"),) + initial_folder=None disabled = False - image_filename=None + image_filename=None image_data=None - image_size=(None, None) - image_subsample=None - border_width=None - size=(None, None) - auto_size_button=None - button_color=None - default_value = None - font=None - bind_return_key=False - focus=False - pad=None - key=None): - -Parameters - - button_text - Text to be displayed on the button - button_type - You should NOT be setting this directly - target - key or (row,col) target for the button - tooltip - tooltip text for the button - file_types - the filetypes that will be used to match files - initial_folder - starting path for folders and files + image_size=(None, None) + image_subsample=None + border_width=None + size=(None, None) + auto_size_button=None + button_color=None + default_value = None + font=None + bind_return_key=False + focus=False + pad=None + key=None): + +Parameters + + button_text - Text to be displayed on the button + button_type - You should NOT be setting this directly + target - key or (row,col) target for the button + tooltip - tooltip text for the button + file_types - the filetypes that will be used to match files + initial_folder - starting path for folders and files disabled = Bool If True button is disabled - image_filename - image filename if there is a button image + image_filename - image filename if there is a button image image_data - in-RAM image to be displayed on button - image_size - size of button image in pixels - image_subsample - amount to reduce the size of the image - border_width - width of border around button in pixels - size - size in characters - auto_size_button - True if button size is determined by button text - button_color - (text color, backound color) - default_value - initial value for buttons that hold information - font - font to use for button text - bind_return_key - If True the return key will cause this button to fire - focus - if focus should be set to this button - pad - (x,y) padding in pixels for packing the button - key - key used for finding the element - -#### Pre-defined Buttons -These Pre-made buttons are some of the most important elements of all because they are used so much. They all basically do the same thing, set the button text to match the function name and set the parameters to commonly used values. If you find yourself needing to create a custom button often because it's not on this list, please post a request on GitHub. . They include: - - OK - Ok - Submit - Cancel - Yes - No - Exit - Quit - Help - Save - SaveAs - FileBrowse - FilesBrowse - FileSaveAs - FolderBrowse -. - layout = [[sg.OK(), sg.Cancel()]] - -![ok cancel 3](https://user-images.githubusercontent.com/13696193/44959927-aa5f5e80-aec4-11e8-86e1-5dc0b3a2b803.jpg) - - #### Button targets - -The `FileBrowse`, `FolderBrowse`, `FileSaveAs` , `FilesSaveAs`, `CalendarButton`, `ColorChooserButton` buttons all fill-in values into another element located on the window. The target can be a Text Element or an InputText Element. The location of the element is specified by the `target` variable in the function call. - -The Target comes in two forms. -1. Key -2. (row, column) - -Targets that are specified using a key will find its target element by using the target's key value. This is the "preferred" method. - -If the Target is specified using (row, column) then it utilizes a grid system. The rows in your GUI are numbered starting with 0. The target can be specified as a hard coded grid item or it can be relative to the button. - -The (row, col) targeting can only target elements that are in the same "container". Containers are the Window, Column and Frame Elements. A File Browse button located inside of a Column is unable to target elements outside of that Column. - -The default value for `target` is `(ThisRow, -1)`. `ThisRow` is a special value that tells the GUI to use the same row as the button. The Y-value of -1 means the field one value to the left of the button. For a File or Folder Browse button, the field that it fills are generally to the left of the button is most cases. (ThisRow, -1) means the Element to the left of the button, on the same row. - -If a value of `(None, None)` is chosen for the target, then the button itself will hold the information. Later the button can be queried for the value by using the button's key. - -Let's examine this window as an example: - - -![file browse](https://user-images.githubusercontent.com/13696193/44959944-d1b62b80-aec4-11e8-8a68-9d79d37b2c81.jpg) - - -The `InputText` element is located at (1,0)... row 1, column 0. The `Browse` button is located at position (2,0). The Target for the button could be any of these values: - - Target = (1,0) - Target = (-1,0) - -The code for the entire window could be: - - layout = [[sg.T('Source Folder')], - [sg.In()], - [sg.FolderBrowse(target=(-1, 0)), sg.OK()]] - -or if using keys, then the code would be: - - layout = [[sg.T('Source Folder')], - [sg.In(key='input')], - [sg.FolderBrowse(target='input'), sg.OK()]] - -See how much easier the key method is? - -**Save & Open Buttons** - -There are 3 different types of File/Folder open dialog box available. If you are looking for a file to open, the `FileBrowse` is what you want. If you want to save a file, `SaveAs` is the button. If you want to get a folder name, then `FolderBrowse` is the button to use. To open several files at once, use the `FilesBrowse` button. It will create a list of files that are separated by ';' - - -![open](https://user-images.githubusercontent.com/13696193/45243804-2b529780-b2c3-11e8-90dc-6c9061db2a1e.jpg) - - -![folder](https://user-images.githubusercontent.com/13696193/45243805-2b529780-b2c3-11e8-95ee-fec3c0b11319.jpg) - - -![saveas](https://user-images.githubusercontent.com/13696193/45243807-2beb2e00-b2c3-11e8-8549-ba71cdc05951.jpg) - - - -**Calendar Buttons** - -These buttons pop up a calendar chooser window. The chosen date is returned as a string. - -![calendar](https://user-images.githubusercontent.com/13696193/45243374-99965a80-b2c1-11e8-8311-49777835ca40.jpg) - -**Color Chooser Buttons** - -These buttons pop up a standard color chooser window. The result is returned as a tuple. One of the returned values is an RGB hex representation. - -![color](https://user-images.githubusercontent.com/13696193/45243375-99965a80-b2c1-11e8-9779-b71bed85fab6.jpg) - - -**Custom Buttons** -Not all buttons are created equal. A button that closes a window is different that a button that returns from the window without closing it. If you want to define your own button, you will generally do this with the Button Element `Button`, which closes the window when clicked. - -layout = [[sg.Button('My Button')]] - -![button](https://user-images.githubusercontent.com/13696193/44959862-b696ec00-aec3-11e8-9e88-4b9af0338a03.jpg) - -All buttons can have their text changed by changing the `button_text` variable in the button call. It is this text that is returned when a window is read. This text will be what tells you which button is called so make it unique. Most of the convenience buttons (Submit, Cancel, Yes, etc) are all Buttons. Some that are not are `FileBrowse` , `FolderBrowse`, `FileSaveAs`. They clearly do not close the window. Instead they bring up a file or folder browser dialog box. - -**Button Images** -Now this is an exciting feature not found in many simplified packages.... images on buttons! You can make a pretty spiffy user interface with the help of a few button images. - -Your button images need to be in PNG or GIF format. When you make a button with an image, set the button background to the same color as the background. There's a button color TRANSPARENT_BUTTON that you can set your button color to in order for it to blend into the background. Note that this value is currently the same as the color as the default system background on Windows. - -This example comes from the `Demo Media Player.py` example program. Because it's a non-blocking button, it's defined as `RButton`. You also put images on blocking buttons by using `Button`. - - - sg.RButton('Restart Song', button_color=sg.TRANSPARENT_BUTTON, - image_filename=image_restart, image_size=(50, 50), image_subsample=2, border_width=0) - -Three parameters are used for button images. - - image_filename - Filename. Can be a relative path - image_size - Size of image file in pixels - image_subsample - Amount to divide the size by. 2 means your image will be 1/2 the size. 3 means 1/3 - -Here's an example window made with button images. - -![media file player](https://user-images.githubusercontent.com/13696193/43161977-9ee7cace-8f57-11e8-8ff8-3ea24b69dab9.jpg) - -You'll find the source code in the file Demo Media Player. Here is what the button calls look like to create media player window - - sg.RButton('Pause', button_color=sg.TRANSPARENT_BUTTON, - image_filename=image_pause, image_size=(50, 50), image_subsample=2, border_width=0) - -This is one you'll have to experiment with at this point. Not up for an exhaustive explanation. - - **Realtime Buttons** - - Normally buttons are considered "clicked" when the mouse button is let UP after a downward click on the button. What about times when you need to read the raw up/down button values. A classic example for this is a robotic remote control. Building a remote control using a GUI is easy enough. One button for each of the directions is a start. Perhaps something like this: - -![robot remote](https://user-images.githubusercontent.com/13696193/44959958-ff9b7000-aec4-11e8-99ea-7450926409be.jpg) - - -This window has 2 button types. There's the normal "Simple Button" (Quit) and 4 "Realtime Buttons". - -Here is the code to make, show and get results from this window: - - window = sg.Window('Robotics Remote Control', auto_size_text=True) - - window_rows = [[sg.Text('Robotics Remote Control')], - [sg.T(' '*10), sg.RealtimeButton('Forward')], - [ sg.RealtimeButton('Left'), sg.T(' '*15), sg.RealtimeButton('Right')], - [sg.T(' '*10), sg.RealtimeButton('Reverse')], - [sg.T('')], - [sg.Quit(button_color=('black', 'orange'))] - ] - - window.Layout(window_rows, non_blocking=True).Read() - -Somewhere later in your code will be your main event loop. This is where you do your polling of devices, do input/output, etc. It's here that you will read your window's buttons. - - while (True): - # This is the code that reads and updates your window - button, values = window.ReadNonBlocking() - if button is not None: - sg.Print(button) - if button == 'Quit' or values is None: - break - time.sleep(.01) - -This loop will read button values and print them. When one of the Realtime buttons is clicked, the call to `window.ReadNonBlocking` will return a button name matching the name on the button that was depressed. It will continue to return values as long as the button remains depressed. Once released, the ReadNonBlocking will return None for buttons until a button is again clicked. - -**File Types** -The `FileBrowse` & `SaveAs` buttons have an additional setting named `file_types`. This variable is used to filter the files shown in the file dialog box. The default value for this setting is - - FileTypes=(("ALL Files", "*.*"),) - -This code produces a window where the Browse button only shows files of type .TXT - - layout = [[sg.In() ,sg.FileBrowse(file_types=(("Text Files", "*.txt"),))]] - - ***The ENTER key*** - The ENTER key is an important part of data entry for windows. There's a long tradition of the enter key being used to quickly submit windows. PySimpleGUI implements this by tying the ENTER key to the first button that closes or reads a window. - -The Enter Key can be "bound" to a particular button so that when the key is pressed, it causes the window to return as if the button was clicked. This is done using the `bind_return_key` parameter in the button calls. -If there are more than 1 button on a window, the FIRST button that is of type Close window or Read window is used. First is determined by scanning the window, top to bottom and left to right. - + image_size - size of button image in pixels + image_subsample - amount to reduce the size of the image + border_width - width of border around button in pixels + size - size in characters + auto_size_button - True if button size is determined by button text + button_color - (text color, backound color) + default_value - initial value for buttons that hold information + font - font to use for button text + bind_return_key - If True the return key will cause this button to fire + focus - if focus should be set to this button + pad - (x,y) padding in pixels for packing the button + key - key used for finding the element + +#### Pre-defined Buttons +These Pre-made buttons are some of the most important elements of all because they are used so much. They all basically do the same thing, set the button text to match the function name and set the parameters to commonly used values. If you find yourself needing to create a custom button often because it's not on this list, please post a request on GitHub. . They include: + + OK + Ok + Submit + Cancel + Yes + No + Exit + Quit + Help + Save + SaveAs + FileBrowse + FilesBrowse + FileSaveAs + FolderBrowse +. + layout = [[sg.OK(), sg.Cancel()]] + +![ok cancel 3](https://user-images.githubusercontent.com/13696193/44959927-aa5f5e80-aec4-11e8-86e1-5dc0b3a2b803.jpg) + + #### Button targets + +The `FileBrowse`, `FolderBrowse`, `FileSaveAs` , `FilesSaveAs`, `CalendarButton`, `ColorChooserButton` buttons all fill-in values into another element located on the window. The target can be a Text Element or an InputText Element. The location of the element is specified by the `target` variable in the function call. + +The Target comes in two forms. +1. Key +2. (row, column) + +Targets that are specified using a key will find its target element by using the target's key value. This is the "preferred" method. + +If the Target is specified using (row, column) then it utilizes a grid system. The rows in your GUI are numbered starting with 0. The target can be specified as a hard coded grid item or it can be relative to the button. + +The (row, col) targeting can only target elements that are in the same "container". Containers are the Window, Column and Frame Elements. A File Browse button located inside of a Column is unable to target elements outside of that Column. + +The default value for `target` is `(ThisRow, -1)`. `ThisRow` is a special value that tells the GUI to use the same row as the button. The Y-value of -1 means the field one value to the left of the button. For a File or Folder Browse button, the field that it fills are generally to the left of the button is most cases. (ThisRow, -1) means the Element to the left of the button, on the same row. + +If a value of `(None, None)` is chosen for the target, then the button itself will hold the information. Later the button can be queried for the value by using the button's key. + +Let's examine this window as an example: + + +![file browse](https://user-images.githubusercontent.com/13696193/44959944-d1b62b80-aec4-11e8-8a68-9d79d37b2c81.jpg) + + +The `InputText` element is located at (1,0)... row 1, column 0. The `Browse` button is located at position (2,0). The Target for the button could be any of these values: + + Target = (1,0) + Target = (-1,0) + +The code for the entire window could be: + + layout = [[sg.T('Source Folder')], + [sg.In()], + [sg.FolderBrowse(target=(-1, 0)), sg.OK()]] + +or if using keys, then the code would be: + + layout = [[sg.T('Source Folder')], + [sg.In(key='input')], + [sg.FolderBrowse(target='input'), sg.OK()]] + +See how much easier the key method is? + +**Save & Open Buttons** + +There are 3 different types of File/Folder open dialog box available. If you are looking for a file to open, the `FileBrowse` is what you want. If you want to save a file, `SaveAs` is the button. If you want to get a folder name, then `FolderBrowse` is the button to use. To open several files at once, use the `FilesBrowse` button. It will create a list of files that are separated by ';' + + +![open](https://user-images.githubusercontent.com/13696193/45243804-2b529780-b2c3-11e8-90dc-6c9061db2a1e.jpg) + + +![folder](https://user-images.githubusercontent.com/13696193/45243805-2b529780-b2c3-11e8-95ee-fec3c0b11319.jpg) + + +![saveas](https://user-images.githubusercontent.com/13696193/45243807-2beb2e00-b2c3-11e8-8549-ba71cdc05951.jpg) + + + +**Calendar Buttons** + +These buttons pop up a calendar chooser window. The chosen date is returned as a string. + +![calendar](https://user-images.githubusercontent.com/13696193/45243374-99965a80-b2c1-11e8-8311-49777835ca40.jpg) + +**Color Chooser Buttons** + +These buttons pop up a standard color chooser window. The result is returned as a tuple. One of the returned values is an RGB hex representation. + +![color](https://user-images.githubusercontent.com/13696193/45243375-99965a80-b2c1-11e8-9779-b71bed85fab6.jpg) + + +**Custom Buttons** +Not all buttons are created equal. A button that closes a window is different that a button that returns from the window without closing it. If you want to define your own button, you will generally do this with the Button Element `Button`, which closes the window when clicked. + +layout = [[sg.Button('My Button')]] + +![button](https://user-images.githubusercontent.com/13696193/44959862-b696ec00-aec3-11e8-9e88-4b9af0338a03.jpg) + +All buttons can have their text changed by changing the `button_text` variable in the button call. It is this text that is returned when a window is read. This text will be what tells you which button is called so make it unique. Most of the convenience buttons (Submit, Cancel, Yes, etc) are all Buttons. Some that are not are `FileBrowse` , `FolderBrowse`, `FileSaveAs`. They clearly do not close the window. Instead they bring up a file or folder browser dialog box. + +**Button Images** +Now this is an exciting feature not found in many simplified packages.... images on buttons! You can make a pretty spiffy user interface with the help of a few button images. + +Your button images need to be in PNG or GIF format. When you make a button with an image, set the button background to the same color as the background. There's a button color TRANSPARENT_BUTTON that you can set your button color to in order for it to blend into the background. Note that this value is currently the same as the color as the default system background on Windows. + +This example comes from the `Demo Media Player.py` example program. Because it's a non-blocking button, it's defined as `RButton`. You also put images on blocking buttons by using `Button`. + + + sg.RButton('Restart Song', button_color=sg.TRANSPARENT_BUTTON, + image_filename=image_restart, image_size=(50, 50), image_subsample=2, border_width=0) + +Three parameters are used for button images. + + image_filename - Filename. Can be a relative path + image_size - Size of image file in pixels + image_subsample - Amount to divide the size by. 2 means your image will be 1/2 the size. 3 means 1/3 + +Here's an example window made with button images. + +![media file player](https://user-images.githubusercontent.com/13696193/43161977-9ee7cace-8f57-11e8-8ff8-3ea24b69dab9.jpg) + +You'll find the source code in the file Demo Media Player. Here is what the button calls look like to create media player window + + sg.RButton('Pause', button_color=sg.TRANSPARENT_BUTTON, + image_filename=image_pause, image_size=(50, 50), image_subsample=2, border_width=0) + +This is one you'll have to experiment with at this point. Not up for an exhaustive explanation. + + **Realtime Buttons** + + Normally buttons are considered "clicked" when the mouse button is let UP after a downward click on the button. What about times when you need to read the raw up/down button values. A classic example for this is a robotic remote control. Building a remote control using a GUI is easy enough. One button for each of the directions is a start. Perhaps something like this: + +![robot remote](https://user-images.githubusercontent.com/13696193/44959958-ff9b7000-aec4-11e8-99ea-7450926409be.jpg) + + +This window has 2 button types. There's the normal "Simple Button" (Quit) and 4 "Realtime Buttons". + +Here is the code to make, show and get results from this window: + + window = sg.Window('Robotics Remote Control', auto_size_text=True) + + window_rows = [[sg.Text('Robotics Remote Control')], + [sg.T(' '*10), sg.RealtimeButton('Forward')], + [ sg.RealtimeButton('Left'), sg.T(' '*15), sg.RealtimeButton('Right')], + [sg.T(' '*10), sg.RealtimeButton('Reverse')], + [sg.T('')], + [sg.Quit(button_color=('black', 'orange'))] + ] + + window.Layout(window_rows, non_blocking=True).Read() + +Somewhere later in your code will be your main event loop. This is where you do your polling of devices, do input/output, etc. It's here that you will read your window's buttons. + + while (True): + # This is the code that reads and updates your window + event, values = window.ReadNonBlocking() + if button is not None: + sg.Print(event) + if event == 'Quit' or values is None: + break + time.sleep(.01) + +This loop will read button values and print them. When one of the Realtime buttons is clicked, the call to `window.ReadNonBlocking` will return a button name matching the name on the button that was depressed. It will continue to return values as long as the button remains depressed. Once released, the ReadNonBlocking will return None for buttons until a button is again clicked. + +**File Types** +The `FileBrowse` & `SaveAs` buttons have an additional setting named `file_types`. This variable is used to filter the files shown in the file dialog box. The default value for this setting is + + FileTypes=(("ALL Files", "*.*"),) + +This code produces a window where the Browse button only shows files of type .TXT + + layout = [[sg.In() ,sg.FileBrowse(file_types=(("Text Files", "*.txt"),))]] + + ***The ENTER key*** + The ENTER key is an important part of data entry for windows. There's a long tradition of the enter key being used to quickly submit windows. PySimpleGUI implements this by tying the ENTER key to the first button that closes or reads a window. + +The Enter Key can be "bound" to a particular button so that when the key is pressed, it causes the window to return as if the button was clicked. This is done using the `bind_return_key` parameter in the button calls. +If there are more than 1 button on a window, the FIRST button that is of type Close window or Read window is used. First is determined by scanning the window, top to bottom and left to right. + ### Button Methods ```python @@ -2223,75 +2275,75 @@ disabled - if True disables the button image_data - sets button image to in-ram image image_filename - sets button image using a file - --- + --- ## ProgressBar Element -The `ProgressBar` element is used to build custom Progress Bar windows. It is HIGHLY recommended that you use OneLineProgressMeter that provides a complete progress meter solution for you. Progress Meters are not easy to work with because the windows have to be non-blocking and they are tricky to debug. - -The **easiest** way to get progress meters into your code is to use the `OneLineProgressMeter` API. This consists of a pair of functions, `OneLineProgressMeter` and `OneLineProgressMeterCancel`. You can easily cancel any progress meter by calling it with the current value = max value. This will mark the meter as expired and close the window. -You've already seen OneLineProgressMeter calls presented earlier in this readme. - - sg.OneLineProgressMeter('My Meter', i+1, 1000, 'key', 'Optional message') - -The return value for `OneLineProgressMeter` is: -`True` if meter updated correctly -`False` if user clicked the Cancel button, closed the window, or vale reached the max value. - -#### Progress Mater in Your window -Another way of using a Progress Meter with PySimpleGUI is to build a custom window with a `ProgressBar` Element in the window. You will need to run your window as a non-blocking window. When you are ready to update your progress bar, you call the `UpdateBar` method for the `ProgressBar` element itself. - -![progress custom](https://user-images.githubusercontent.com/13696193/45243969-c3508100-b2c3-11e8-82bc-927d0307e093.jpg) - - import PySimpleGUI as sg - - # layout the window - layout = [[sg.Text('A custom progress meter')], - [sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progressbar')], - [sg.Cancel()]] - - # create the window` - window = sg.Window('Custom Progress Meter').Layout(layout) - progress_bar = window.FindElement('progressbar') - # loop that would normally do something useful - for i in range(10000): - # check to see if the cancel button was clicked and exit loop if clicked - button, values = window.ReadNonBlocking() - if button == 'Cancel' or values == None: - break - # update bar with loop value +1 so that bar eventually reaches the maximum - progress_bar.UpdateBar(i + 1) - # done with loop... need to destroy the window as it's still open - window.CloseNonBlocking()) - - -#### Output -The Output Element is a re-direction of Stdout. Anything "printed" will be displayed in this element. - - Output(size=(None, None)) - -Here's a complete solution for a chat-window using an Async window with an Output Element - - import PySimpleGUI as sg - - # Blocking window that doesn't close - def ChatBot(): - layout = [[(sg.Text('This is where standard out is being routed', size=[40, 1]))], - [sg.Output(size=(80, 20))], - [sg.Multiline(size=(70, 5), enter_submits=True), - sg.RButton('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0])), - sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]] - - window = sg.Window('Chat Window', default_element_size=(30, 2)).Layout(layout) - - # ---===--- Loop taking in user input and using it to query HowDoI web oracle --- # - while True: - button, value = window.Read() - if button == 'SEND': - print(value) - else: - break - - ChatBot() - +The `ProgressBar` element is used to build custom Progress Bar windows. It is HIGHLY recommended that you use OneLineProgressMeter that provides a complete progress meter solution for you. Progress Meters are not easy to work with because the windows have to be non-blocking and they are tricky to debug. + +The **easiest** way to get progress meters into your code is to use the `OneLineProgressMeter` API. This consists of a pair of functions, `OneLineProgressMeter` and `OneLineProgressMeterCancel`. You can easily cancel any progress meter by calling it with the current value = max value. This will mark the meter as expired and close the window. +You've already seen OneLineProgressMeter calls presented earlier in this readme. + + sg.OneLineProgressMeter('My Meter', i+1, 1000, 'key', 'Optional message') + +The return value for `OneLineProgressMeter` is: +`True` if meter updated correctly +`False` if user clicked the Cancel button, closed the window, or vale reached the max value. + +#### Progress Mater in Your window +Another way of using a Progress Meter with PySimpleGUI is to build a custom window with a `ProgressBar` Element in the window. You will need to run your window as a non-blocking window. When you are ready to update your progress bar, you call the `UpdateBar` method for the `ProgressBar` element itself. + +![progress custom](https://user-images.githubusercontent.com/13696193/45243969-c3508100-b2c3-11e8-82bc-927d0307e093.jpg) + + import PySimpleGUI as sg + + # layout the window + layout = [[sg.Text('A custom progress meter')], + [sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progressbar')], + [sg.Cancel()]] + + # create the window` + window = sg.Window('Custom Progress Meter').Layout(layout) + progress_bar = window.FindElement('progressbar') + # loop that would normally do something useful + for i in range(10000): + # check to see if the cancel button was clicked and exit loop if clicked + event, values = window.ReadNonBlocking() + if event == 'Cancel' or values == None: + break + # update bar with loop value +1 so that bar eventually reaches the maximum + progress_bar.UpdateBar(i + 1) + # done with loop... need to destroy the window as it's still open + window.CloseNonBlocking()) + + +#### Output +The Output Element is a re-direction of Stdout. Anything "printed" will be displayed in this element. + + Output(size=(None, None)) + +Here's a complete solution for a chat-window using an Async window with an Output Element + + import PySimpleGUI as sg + + # Blocking window that doesn't close + def ChatBot(): + layout = [[(sg.Text('This is where standard out is being routed', size=[40, 1]))], + [sg.Output(size=(80, 20))], + [sg.Multiline(size=(70, 5), enter_submits=True), + sg.RButton('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0])), + sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]] + + window = sg.Window('Chat Window', default_element_size=(30, 2)).Layout(layout) + + # ---===--- Loop taking in user input and using it to query HowDoI web oracle --- # + while True: + event, value = window.Read() + if event == 'SEND': + print(value) + else: + break + + ChatBot() + ### ProgressBar Methods ```python UpdateBar(current_count, max=None) @@ -2301,190 +2353,190 @@ max - changes the max value ## Column Element -Starting in version 2.9 you'll be able to do more complex layouts by using the Column Element. Think of a Column as a window within a window. And, yes, you can have a Column within a Column if you want. - -Columns are specified in exactly the same way as a window is, as a list of lists. - - def Column(layout - the list of rows that define the layout - background_color - color of background - size - size of visible portion of column - pad - element padding to use when packing - scrollable - bool. True if should add scrollbars - - -Columns are needed when you have an element that has a height > 1 line on the left, with single-line elements on the right. Here's an example of this kind of layout: - - -![column](https://user-images.githubusercontent.com/13696193/44959988-66b92480-aec5-11e8-9c26-316ed24a68c0.jpg) - - -This code produced the above window. - - - import PySimpleGUI as sg - - # Demo of how columns work - # window has on row 1 a vertical slider followed by a COLUMN with 7 rows - # Prior to the Column element, this layout was not possible - # Columns layouts look identical to window layouts, they are a list of lists of elements. - - window = sg.Window('Columns') # blank window - - # Column layout - col = [[sg.Text('col Row 1')], - [sg.Text('col Row 2'), sg.Input('col input 1')], - [sg.Text('col Row 3'), sg.Input('col input 2')], - [sg.Text('col Row 4'), sg.Input('col input 3')], - [sg.Text('col Row 5'), sg.Input('col input 4')], - [sg.Text('col Row 6'), sg.Input('col input 5')], - [sg.Text('col Row 7'), sg.Input('col input 6')]] - - layout = [[sg.Slider(range=(1,100), default_value=10, orientation='v', size=(8,20)), sg.Column(col)], - [sg.In('Last input')], - [sg.OK()]] - - # Display the window and get values - # If you're willing to not use the "context manager" design pattern, then it's possible - # to collapse the window display and read down to a single line of code. - button, values = sg.Window('Compact 1-line window with column').Layout(layout).Read() - - sg.Popup(button, values, line_width=200) - -The Column Element has 1 required parameter and 1 optional (the layout and the background color). Setting the background color has the same effect as setting the window's background color, except it only affects the column rectangle. - - Column(layout, background_color=None) - -The default background color for Columns is the same as the default window background color. If you change the look and feel of the window, the column background will match the window background automatically. - - - ----- -## Frame Element (Labelled Frames, Frames with a title) - -Frames work exactly the same way as Columns. You create layout that is then used to initialize the Frame. - - def Frame(title - the label / title to put on frame - layout - list of rows of elements the frame contains - title_color - color of the title text - background_color - color of background - title_location - locations to put the title - relief - type of relief to use - size - size of Frame in characters. Do not use if you want frame to autosize - font - font to use for title - pad - element padding to use when packing - border_width - how thick the line going around frame should be - key - key used to location the element - tooltip - tooltip text - - - -This code creates a window with a Frame and 2 buttons. - - frame_layout = [ - [sg.T('Text inside of a frame')], - [sg.CB('Check 1'), sg.CB('Check 2')], - ] - layout = [ - [sg.Frame('My Frame Title', frame_layout, font='Any 12', title_color='blue')], - [sg.Submit(), sg.Cancel()] - ] - - window = sg.Window('Frame with buttons', font=("Helvetica", 12)).Layout(layout) - - -![frame element](https://user-images.githubusercontent.com/13696193/45889173-c2245700-bd8d-11e8-8f73-1e5f1be3ddb1.jpg) - - - -Notice how the Frame layout looks identical to a window layout. A window works exactly the same way as a Column and a Frame. They all are "container elements". Elements that contain other elements. - -*These container Elements can be nested as deep as you want.* That's a pretty spiffy feature, right? Took a lot of work so be appreciative. Recursive code isn't trivial. - -## Canvas Element - -In my opinion, the tkinter Canvas Widget is the most powerful of the tkinter widget. While I try my best to completely isolate the user from anything that is tkinter related, the Canvas Element is the one exception. It enables integration with a number of other packages, often with spectacular results. - -### Matplotlib, Pyplot Integration - -One such integration is with Matploplib and Pyplot. There is a Demo program written that you can use as a design pattern to get an understanding of how to use the Canvas Widget once you get it. - - def Canvas(canvas - a tkinter canvasf if you created one. Normally not set - background_color - canvas color - size - size in pixels - pad - element padding for packing - key - key used to lookup element - tooltip - tooltip text - -The order of operations to obtain a tkinter Canvas Widget is: - - figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds - # define the window layout - layout = [[sg.Text('Plot test')], - [sg.Canvas(size=(figure_w, figure_h), key='canvas')], - [sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]] - - # create the window and show it without the plot - window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout).Finalize() - - - # add the plot to the window - fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig) - - # show it all again and get buttons - button, values = window.Read() - -To get a tkinter Canvas Widget from PySimpleGUI, follow these steps: -* Add Canvas Element to your window -* Layout your window -* Call `window.Finalize()` - this is a critical step you must not forget -* Find the Canvas Element by looking up using key -* Your Canvas Widget Object will be the found_element.TKCanvas -* Draw on your canvas to your heart's content -* Call `window.Read()` - Nothing will appear on your canvas until you call Read - -See `Demo_Matplotlib.py` for a Recipe you can copy. - - +Starting in version 2.9 you'll be able to do more complex layouts by using the Column Element. Think of a Column as a window within a window. And, yes, you can have a Column within a Column if you want. + +Columns are specified in exactly the same way as a window is, as a list of lists. + + def Column(layout - the list of rows that define the layout + background_color - color of background + size - size of visible portion of column + pad - element padding to use when packing + scrollable - bool. True if should add scrollbars + + +Columns are needed when you have an element that has a height > 1 line on the left, with single-line elements on the right. Here's an example of this kind of layout: + + +![column](https://user-images.githubusercontent.com/13696193/44959988-66b92480-aec5-11e8-9c26-316ed24a68c0.jpg) + + +This code produced the above window. + + + import PySimpleGUI as sg + + # Demo of how columns work + # window has on row 1 a vertical slider followed by a COLUMN with 7 rows + # Prior to the Column element, this layout was not possible + # Columns layouts look identical to window layouts, they are a list of lists of elements. + + window = sg.Window('Columns') # blank window + + # Column layout + col = [[sg.Text('col Row 1')], + [sg.Text('col Row 2'), sg.Input('col input 1')], + [sg.Text('col Row 3'), sg.Input('col input 2')], + [sg.Text('col Row 4'), sg.Input('col input 3')], + [sg.Text('col Row 5'), sg.Input('col input 4')], + [sg.Text('col Row 6'), sg.Input('col input 5')], + [sg.Text('col Row 7'), sg.Input('col input 6')]] + + layout = [[sg.Slider(range=(1,100), default_value=10, orientation='v', size=(8,20)), sg.Column(col)], + [sg.In('Last input')], + [sg.OK()]] + + # Display the window and get values + # If you're willing to not use the "context manager" design pattern, then it's possible + # to collapse the window display and read down to a single line of code. + event, values = sg.Window('Compact 1-line window with column').Layout(layout).Read() + + sg.Popup(event, values, line_width=200) + +The Column Element has 1 required parameter and 1 optional (the layout and the background color). Setting the background color has the same effect as setting the window's background color, except it only affects the column rectangle. + + Column(layout, background_color=None) + +The default background color for Columns is the same as the default window background color. If you change the look and feel of the window, the column background will match the window background automatically. + + + +---- +## Frame Element (Labelled Frames, Frames with a title) + +Frames work exactly the same way as Columns. You create layout that is then used to initialize the Frame. + + def Frame(title - the label / title to put on frame + layout - list of rows of elements the frame contains + title_color - color of the title text + background_color - color of background + title_location - locations to put the title + relief - type of relief to use + size - size of Frame in characters. Do not use if you want frame to autosize + font - font to use for title + pad - element padding to use when packing + border_width - how thick the line going around frame should be + key - key used to location the element + tooltip - tooltip text + + + +This code creates a window with a Frame and 2 buttons. + + frame_layout = [ + [sg.T('Text inside of a frame')], + [sg.CB('Check 1'), sg.CB('Check 2')], + ] + layout = [ + [sg.Frame('My Frame Title', frame_layout, font='Any 12', title_color='blue')], + [sg.Submit(), sg.Cancel()] + ] + + window = sg.Window('Frame with buttons', font=("Helvetica", 12)).Layout(layout) + + +![frame element](https://user-images.githubusercontent.com/13696193/45889173-c2245700-bd8d-11e8-8f73-1e5f1be3ddb1.jpg) + + + +Notice how the Frame layout looks identical to a window layout. A window works exactly the same way as a Column and a Frame. They all are "container elements". Elements that contain other elements. + +*These container Elements can be nested as deep as you want.* That's a pretty spiffy feature, right? Took a lot of work so be appreciative. Recursive code isn't trivial. + +## Canvas Element + +In my opinion, the tkinter Canvas Widget is the most powerful of the tkinter widget. While I try my best to completely isolate the user from anything that is tkinter related, the Canvas Element is the one exception. It enables integration with a number of other packages, often with spectacular results. + +### Matplotlib, Pyplot Integration + +One such integration is with Matploplib and Pyplot. There is a Demo program written that you can use as a design pattern to get an understanding of how to use the Canvas Widget once you get it. + + def Canvas(canvas - a tkinter canvasf if you created one. Normally not set + background_color - canvas color + size - size in pixels + pad - element padding for packing + key - key used to lookup element + tooltip - tooltip text + +The order of operations to obtain a tkinter Canvas Widget is: + + figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds + # define the window layout + layout = [[sg.Text('Plot test')], + [sg.Canvas(size=(figure_w, figure_h), key='canvas')], + [sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]] + + # create the window and show it without the plot + window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout).Finalize() + + + # add the plot to the window + fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig) + + # show it all again and get buttons + event, values = window.Read() + +To get a tkinter Canvas Widget from PySimpleGUI, follow these steps: +* Add Canvas Element to your window +* Layout your window +* Call `window.Finalize()` - this is a critical step you must not forget +* Find the Canvas Element by looking up using key +* Your Canvas Widget Object will be the found_element.TKCanvas +* Draw on your canvas to your heart's content +* Call `window.Read()` - Nothing will appear on your canvas until you call Read + +See `Demo_Matplotlib.py` for a Recipe you can copy. + + ### Canvas Methods TKCanvas - not a method but a property. Returns the tkinter Canvas Widget -## Graph Element - -All you math fans will enjoy this Element... and all you non-math fans will enjoy it too. - -I've found nothing to be less fun than dealing with a graphic's coordinate system from a GUI Framework. It's always upside down from what I want. (0,0) is in the upper left hand corner. In short, it's a **pain in the ass**. - -Graph Element to the rescue. A Graph Element creates a pixel addressable canvas using YOUR coordinate system. *You* get to define the units on the X and Y axis. - -There are 3 values you'll need to supply the Graph Element. They are: -* Size of the canvas in pixels -* The lower left (x,y) coordinate of your coordinate system -* The upper right (x,y) coordinate of your coordinate system - -After you supply those values you can scribble all of over your graph by creating Graph Figures. Graph Figures are created, and a Figure ID is obtained by calling: -* DrawCircle -* DrawLine -* DrawPoint -* DrawRectangle -* DrawOval - -You can move your figures around on the canvas by supplying the Figure ID the x,y amount to move. - - graph.MoveFigure(my_circle, 10, 10) - -This Element is relatively new and may have some parameter additions or deletions. It shouldn't break your code however. - - def Graph( canvas_size - size of canvas in pixels - graph_bottom_left - the x,y location of your coordinate system's bottom left point - graph_top_right - the x,y location of your coordinate system's top right point - background_color - color to use for background - pad - element padding for pack - key - key used to lookup element - tooltip - tooltip text - +## Graph Element + +All you math fans will enjoy this Element... and all you non-math fans will enjoy it too. + +I've found nothing to be less fun than dealing with a graphic's coordinate system from a GUI Framework. It's always upside down from what I want. (0,0) is in the upper left hand corner. In short, it's a **pain in the ass**. + +Graph Element to the rescue. A Graph Element creates a pixel addressable canvas using YOUR coordinate system. *You* get to define the units on the X and Y axis. + +There are 3 values you'll need to supply the Graph Element. They are: +* Size of the canvas in pixels +* The lower left (x,y) coordinate of your coordinate system +* The upper right (x,y) coordinate of your coordinate system + +After you supply those values you can scribble all of over your graph by creating Graph Figures. Graph Figures are created, and a Figure ID is obtained by calling: +* DrawCircle +* DrawLine +* DrawPoint +* DrawRectangle +* DrawOval + +You can move your figures around on the canvas by supplying the Figure ID the x,y amount to move. + + graph.MoveFigure(my_circle, 10, 10) + +This Element is relatively new and may have some parameter additions or deletions. It shouldn't break your code however. + + def Graph( canvas_size - size of canvas in pixels + graph_bottom_left - the x,y location of your coordinate system's bottom left point + graph_top_right - the x,y location of your coordinate system's top right point + background_color - color to use for background + pad - element padding for pack + key - key used to lookup element + tooltip - tooltip text + ### Graph Methods ```python DrawLine(self, point_from, point_to, color='black', width=1) @@ -2514,54 +2566,54 @@ Update - changes background color Move - moves everything an x,y direction MoveFigure - moves an individual figure - -## Table Element - -Let me say up front that the Table Element has Beta status. The reason is that some of the parameters are not quite right and will change. Be warned one or two parameters may change. The `size` parameter in particular is gong to change. Currently the number of rows to allocate for the table is set by the height parameter of size. The problem is that the width is not used. The plan is to instead have a parameter named `number_of_rows` or something like it. - - def Table(values - Your table's array - headings - list of strings representing your headings, if you have any - visible_column_map - list of bools. If True, column in that position is shown. Defaults to all columns - col_widths - list of column widths - def_col_width - default column width. defaults to 10 - auto_size_columns - bool. If True column widths are determined by table contents - max_col_width - maximum width of a column. defaults to 25 - select_mode - table rows can be selected, but doesn't currently do anything - display_row_numbers - bool. If True shows numbers next to rows - scrollable - if True table will be scrolled - font - font for table entries - justification - left, right, center - text_color - color of text - background_color - cell background color - size - (None, number of rows). - pad - element padding for packing - key - key used to lookup element - tooltip - tooltip text - - + +## Table Element + +Let me say up front that the Table Element has Beta status. The reason is that some of the parameters are not quite right and will change. Be warned one or two parameters may change. The `size` parameter in particular is gong to change. Currently the number of rows to allocate for the table is set by the height parameter of size. The problem is that the width is not used. The plan is to instead have a parameter named `number_of_rows` or something like it. + + def Table(values - Your table's array + headings - list of strings representing your headings, if you have any + visible_column_map - list of bools. If True, column in that position is shown. Defaults to all columns + col_widths - list of column widths + def_col_width - default column width. defaults to 10 + auto_size_columns - bool. If True column widths are determined by table contents + max_col_width - maximum width of a column. defaults to 25 + select_mode - table rows can be selected, but doesn't currently do anything + display_row_numbers - bool. If True shows numbers next to rows + scrollable - if True table will be scrolled + font - font for table entries + justification - left, right, center + text_color - color of text + background_color - cell background color + size - (None, number of rows). + pad - element padding for packing + key - key used to lookup element + tooltip - tooltip text + + ## Tree Element The Tree Element and Table Element are close cousins. Many of the parameters found in the Table Element apply to Tree Elements. In particular the heading information, column widths, etc. ``` class Tree(data=None - data in TreeData format - headings=None - list of strings representing your headings - visible_column_map=None - list of bools indicating which columns to display - col_widths=None - list of column widths - col0_width=10 - width of the first column which has the text data - def_col_width=10 - default column width - auto_size_columns=True - if true will autosize columns (currenly only sizes to col heading width) - max_col_width=20 - max width for columns in characters - select_mode=None - not yet used - font=None - the display font - justification='right' - justification for data display - text_color=None- color of text to display - background_color=None - background color - num_rows=None - number of rows to display - pad=None - element padding - key=None - key for element - tooltip=None - tooltip -``` + headings=None - list of strings representing your headings + visible_column_map=None - list of bools indicating which columns to display + col_widths=None - list of column widths + col0_width=10 - width of the first column which has the text data + def_col_width=10 - default column width + auto_size_columns=True - if true will autosize columns (currenly only sizes to col heading width) + max_col_width=20 - max width for columns in characters + select_mode=None - not yet used + font=None - the display font + justification='right' - justification for data display + text_color=None- color of text to display + background_color=None - background color + num_rows=None - number of rows to display + pad=None - element padding + key=None - key for element + tooltip=None - tooltip +``` Unlike Tables there is no standard format for trees. Thus the data structure passed to the Tree Element must be constructed. This is done using the TreeData class. The process is as follows: * Get a TreeData Object @@ -2578,97 +2630,97 @@ This code creates a TreeData object and populates with 3 values ```python treedata = sg.TreeData() -treedata.Insert("", '_A_', 'A', [1,2,3]) -treedata.Insert("", '_B_', 'B', [4,5,6]) +treedata.Insert("", '_A_', 'A', [1,2,3]) +treedata.Insert("", '_B_', 'B', [4,5,6]) treedata.Insert("_A_", '_A1_', 'A1', ['can','be','anything']) ``` Note that you can use the same values for display_text and keys. The only thing you have to watch for is that you cannot repeat keys. - -## Tab and Tab Group Elements - -Tabs have been a part of PySimpleGUI since the initial release. However, the initial implementation applied tabs at the top level only. The entire window had to be tabbed. There with other limitations that came along with that implementation. That all changed in version 3.8.0 with the new elements - Tab and TabGroup. The old implementation of Tabs was removed in version 3.8.0 as well. - -Tabs are another "Container Element". The other Container Elements include: -* Frame -* Column - -You layout a Frame in exactly the same way as a Frame or Column elements, by passing in a list of elements. - -How you place a Tab into a Window is different than Graph or Frame elements. You cannot place a tab directly into a Window's layout. It much first be placed into a TabGroup. The TabGroup can then be placed into the Window. - -Let's look at this Window as an example: - -![tabbed 1](https://user-images.githubusercontent.com/13696193/45992808-b10f6a80-c059-11e8-9746-ac71afd4d3d6.jpg) - -View of second tab: - -![tabbed 2](https://user-images.githubusercontent.com/13696193/45992809-b10f6a80-c059-11e8-94e6-3bf543c9b0bd.jpg) - - -First we have the Tab layout definitions. They mirror what you see in the screen shots. Tab 1 has 1 Text Element in it. Tab 2 has a Text and an Input Element. - - - tab1_layout = [[sg.T('This is inside tab 1')]] - - tab2_layout = [[sg.T('This is inside tab 2')], - [sg.In(key='in')]] - -The layout for the entire window looks like this: - - layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]])], - [sg.RButton('Read')]] - -The Window layout has the TabGroup and within the tab Group are the two Tab elements. - -One important thing to notice about all of these container Elements... they all take a "list of lists" at the layout. They all have a layout that starts with `[[` - -You will want to keep this `[[ ]]` construct in your head a you're debugging your tabbed windows. It's easy to overlook one or two necessary ['s - -As mentioned earlier, the old-style Tabs were limited to being at the Window-level only. In other words, the tabs were equal in size to the entire window. This is not the case with the "new-style" tabs. This is why you're not going to be upset when you discover your old code no longer works with the new PySimpleGUI release. It'll be worth the few moments it'll take to convert your code. - -Check out what's possible with the NEW Tabs! - -![tabs tabs tabs](https://user-images.githubusercontent.com/13696193/45993438-fd0fde80-c05c-11e8-9ed0-742f14d3070f.jpg) - - -Check out Tabs 7 and 8. We've got a Window with a Column containing Tabs 5 and 6. On Tab 6 are... Tabs 7 and 8. - -As of Release 3.8.0, not all of *options* shown in the API definitions of the Tab and TabGroup Elements are working. They are there as placeholders. - -The definition of a TabGroup is - - TabGroup(layout, - title_color=None - background_color=None - font=None - pad=None - border_width=None - change_submits = False - key=None - tooltip=None) - -The definition of a Tab Element is - - Tab(title, - layout, - title_color=None, - background_color=None, - font=None, - pad=None + +## Tab and Tab Group Elements + +Tabs have been a part of PySimpleGUI since the initial release. However, the initial implementation applied tabs at the top level only. The entire window had to be tabbed. There with other limitations that came along with that implementation. That all changed in version 3.8.0 with the new elements - Tab and TabGroup. The old implementation of Tabs was removed in version 3.8.0 as well. + +Tabs are another "Container Element". The other Container Elements include: +* Frame +* Column + +You layout a Frame in exactly the same way as a Frame or Column elements, by passing in a list of elements. + +How you place a Tab into a Window is different than Graph or Frame elements. You cannot place a tab directly into a Window's layout. It much first be placed into a TabGroup. The TabGroup can then be placed into the Window. + +Let's look at this Window as an example: + +![tabbed 1](https://user-images.githubusercontent.com/13696193/45992808-b10f6a80-c059-11e8-9746-ac71afd4d3d6.jpg) + +View of second tab: + +![tabbed 2](https://user-images.githubusercontent.com/13696193/45992809-b10f6a80-c059-11e8-94e6-3bf543c9b0bd.jpg) + + +First we have the Tab layout definitions. They mirror what you see in the screen shots. Tab 1 has 1 Text Element in it. Tab 2 has a Text and an Input Element. + + + tab1_layout = [[sg.T('This is inside tab 1')]] + + tab2_layout = [[sg.T('This is inside tab 2')], + [sg.In(key='in')]] + +The layout for the entire window looks like this: + + layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]])], + [sg.RButton('Read')]] + +The Window layout has the TabGroup and within the tab Group are the two Tab elements. + +One important thing to notice about all of these container Elements... they all take a "list of lists" at the layout. They all have a layout that starts with `[[` + +You will want to keep this `[[ ]]` construct in your head a you're debugging your tabbed windows. It's easy to overlook one or two necessary ['s + +As mentioned earlier, the old-style Tabs were limited to being at the Window-level only. In other words, the tabs were equal in size to the entire window. This is not the case with the "new-style" tabs. This is why you're not going to be upset when you discover your old code no longer works with the new PySimpleGUI release. It'll be worth the few moments it'll take to convert your code. + +Check out what's possible with the NEW Tabs! + +![tabs tabs tabs](https://user-images.githubusercontent.com/13696193/45993438-fd0fde80-c05c-11e8-9ed0-742f14d3070f.jpg) + + +Check out Tabs 7 and 8. We've got a Window with a Column containing Tabs 5 and 6. On Tab 6 are... Tabs 7 and 8. + +As of Release 3.8.0, not all of *options* shown in the API definitions of the Tab and TabGroup Elements are working. They are there as placeholders. + +The definition of a TabGroup is + + TabGroup(layout, + title_color=None + background_color=None + font=None + pad=None + border_width=None + change_submits = False + key=None + tooltip=None) + +The definition of a Tab Element is + + Tab(title, + layout, + title_color=None, + background_color=None, + font=None, + pad=None disabled=False - border_width=None - key=None - tooltip=None) - - -### Reading Tab Groups - -Tab Groups now return a value when a Read returns. They return which tab is currently selected. There is also a change_submits parameter that can be set that causes a Read to return if a Tab in that group is selected / changed. The key or title belonging to the Tab that was switched to will be returned as the value - - + border_width=None + key=None + tooltip=None) + + +### Reading Tab Groups + +Tab Groups now return a value when a Read returns. They return which tab is currently selected. There is also a change_submits parameter that can be set that causes a Read to return if a Tab in that group is selected / changed. The key or title belonging to the Tab that was switched to will be returned as the value + + ### Tab Element Methods ```python Update(disabled = None) @@ -2677,757 +2729,757 @@ WARNING - This Update method does not yet work! ## Colors -Starting in version 2.5 you can change the background colors for the window and the Elements. - -Your windows can go from this: - -![snap0155](https://user-images.githubusercontent.com/13696193/43273879-a9fdc10a-90cb-11e8-8c20-4f6a244ebe2f.jpg) - - -to this... with one function call... - - -![snap0156](https://user-images.githubusercontent.com/13696193/43273880-aa1955e6-90cb-11e8-94b6-673ecdb2698c.jpg) - - - -While you can do it on an element by element or window level basis, the easiest way, by far, is a call to `SetOptions`. - -Be aware that once you change these options they are changed for the rest of your program's execution. All of your windows will have that look and feel, until you change it to something else (which could be the system default colors. - -This call sets all of the different color options. - - SetOptions(background_color='#9FB8AD', - text_element_background_color='#9FB8AD', - element_background_color='#9FB8AD', - scrollbar_color=None, - input_elements_background_color='#F7F3EC', - progress_meter_color = ('green', 'blue') - button_color=('white','#475841')) - - - -# Global Settings -**Global Settings** -Let's have some fun customizing! Make PySimpleGUI look the way you want it to look. You can set the global settings using the function `PySimpleGUI.SetOptions`. Each option has an optional parameter that's used to set it. - - SetOptions(icon=None - button_color=(None,None) - element_size=(None,None), - margins=(None,None), - element_padding=(None,None) - auto_size_text=None - auto_size_buttons=None - font=None - border_width=None - slider_border_width=None - slider_relief=None - slider_orientation=None - autoclose_time=None - message_box_line_width=None - progress_meter_border_depth=None - progress_meter_style=None - progress_meter_relief=None - progress_meter_color=None - progress_meter_size=None - text_justification=None - text_color=None - background_color=None - element_background_color=None - text_element_background_color=None - input_elements_background_color=None - element_text_color=None - input_text_color=None - scrollbar_color=None, text_color=None - debug_win_size=(None,None) - window_location=(None,None) - tooltip_time = None - -Explanation of parameters - - icon - filename of icon used for taskbar and title bar - button_color - button color (foreground, background) - element_size - element size (width, height) in characters - margins - tkinter margins around outsize - element_padding - tkinter padding around each element - auto_size_text - autosize the elements to fit their text - auto_size_buttons - autosize the buttons to fit their text - font - font used for elements - border_width - amount of bezel or border around sunken or raised elements - slider_border_width - changes the way sliders look - slider_relief - changes the way sliders look - slider_orientation - changes orientation of slider - autoclose_time - time in seconds for autoclose boxes - message_box_line_width - number of characers in a line of text in message boxes - progress_meter_border_depth - amount of border around raised or lowered progress meters - progress_meter_style - style of progress meter as defined by tkinter - progress_meter_relief - relief style - progress_meter_color - color of the bar and background of progress meters - progress_meter_size - size in (characters, pixels) - background_color - Color of the main window's background - element_background_color - Background color of the elements - text_element_background_color - Text element background color - input_elements_background_color - Input fields background color - element_text_color - Text color of elements that have text, like Radio Buttons - input_text_color - Color of the text that you type in - scrollbar_color - Color for scrollbars (may not always work) - text_color - Text element default text color - text_justification - justification to use on Text Elements. Values are strings - 'left', 'right', 'center' - debug_win_size - size of the Print output window - window_location - location on the screen (x,y) of window's top left cornder - tooltip_time - time in milliseconds to wait before showing a tooltip. Default is 400ms - - -These settings apply to all windows `SetOptions`. The Row options and Element options will take precedence over these settings. Settings can be thought of as levels of settings with the window-level being the highest and the Element-level the lowest. Thus the levels are: - - - window level - - Row level - - Element level - -Each lower level overrides the settings of the higher level. Once settings have been changed, they remain changed for the duration of the program (unless changed again). - -# Persistent windows (Window stays open after button click) - -There are 2 ways to keep a window open after the user has clicked a button. One way is to use non-blocking windows (see the next section). The other way is to use buttons that 'read' the window instead of 'close' the window when clicked. The typical buttons you find in windows, including the shortcut buttons, close the window. These include OK, Cancel, Submit, etc. The Button Element also closes the window. - -The `RButton` Element creates a button that when clicked will return control to the user, but will leave the window open and visible. This button is also used in Non-Blocking windows. The difference is in which call is made to read the window. The `Read` call will block, the `ReadNonBlocking` will not block. - - - -## Asynchronous (Non-Blocking) windows -So you want to be a wizard do ya? Well go boldly! - -Use async windows sparingly. It's possible to have a window that appears to be async, but it is not. **Please** try to find other methods before going to async windows. The reason for this plea is that async windows poll tkinter over and over. If you do not have a sleep in your loop, you will eat up 100% of the CPU time. - -When to use a non-blocking window: -* A media file player like an MP3 player -* A status dashboard that's periodically updated -* Progress Meters - when you want to make your own progress meters -* Output using print to a scrolled text element. Good for debugging. - -If your application doesn't follow the basic design pattern at one of those, then it shouldn't be executed as a non-blocking window. - - -### Instead of ReadNonBlocking --- Use `change_submits = True` or return_keyboard_events = True - -Any time you are thinking "I want an X Element to cause a Y Element to do something", then you want to use the `change_submits` option. - -***Instead of polling, try options that cause the window to return to you.*** By using non-blocking windows, you are *polling*. You can indeed create your application by polling. It will work. But you're going to be maxing out your processor and may even take longer to react to an event than if you used another technique. - -**Examples** - -One example is you have an input field that changes as you press buttons on an on-screen keypad. - -![keypad 3](https://user-images.githubusercontent.com/13696193/45260275-a2198e80-b3b0-11e8-85fe-a4ce6484510f.jpg) - - - - -### Periodically Calling`ReadNonBlocking` - -Periodically "refreshing" the visible GUI. The longer you wait between updates to your GUI the more sluggish your windows will feel. It is up to you to make these calls or your GUI will freeze. - -There are 2 methods of interacting with non-blocking windows. -1. Read the window just as you would a normal window -2. "Refresh" the window's values without reading the window. It's a quick operation meant to show the user the latest values - - With asynchronous windows the window is shown, user input is read, but your code keeps right on chugging. YOUR responsibility is to call `PySimpleGUI.ReadNonBlocking` on a periodic basis. Once a second or more will produce a reasonably snappy GUI. - - #### Exiting a Non-Blocking window - -It's important to always provide a "way out" for your user. Make sure you have provided a button or some other mechanism to exit. Also be sure to check for closed windows in your code. It is possible for a window to look closed, but continue running your event loop. - -Typically when reading a window you check `if Button is None` to determine if a window was closed. With NonBlocking windows, buttons will be None unless a button or a key was returned. The way you determine if a window was closed in a non-blocking window is to check **both** the button and the values are None. Since button is normally None, you only need to test for `value is None` in your code. - -The proper code to check if the user has exited the window will be a polling-loop that looks something like this: - - while True: - button, values = window.ReadNonBlocking() - if values is None or button == 'Quit': - break - - -We're going to build an app that does the latter. It's going to update our window with a running clock. - -The basic flow and functions you will be calling are: -Setup - - window = Window() - window_rows = ..... - window.Layout(window_rows, non_blocking=True).Read() - - -Periodic refresh - - window.ReadNonBlocking() or window.Refresh() - -If you need to close the window - - window.CloseNonBlocking() - -Rather than the usual `window.Layout().Read()` call, we're manually adding the rows (doing the layout) and then showing the window. After the window is shown, you simply call `window.ReadNonBlocking()` every now and then. - -When you are ready to close the window (assuming the window wasn't closed by the user or a button click) you simply call `window.CloseNonBlocking()` - -**Example - Running timer that updates** -See the sample code on the GitHub named Demo Media Player for another example of Async windows. We're going to make a window and update one of the elements of that window every .01 seconds. Here's the entire code to do that. - - - import PySimpleGUI as sg - import time - - # window that doesn't block - # Make a window, but don't use context manager - window = sg.Window('Running Timer', auto_size_text=True) - - # Create the layout - window_rows = [[sg.Text('Non-blocking GUI with updates')], - [sg.Text('', size=(8, 2), font=('Helvetica', 20), key='output') ], - [sg.Button('Quit')]] - # Layout the rows of the window and perform a read. Indicate the window is non-blocking! - window.Layout(window_rows).ReadNonBlocking() - - # - # Some place later in your code... - # You need to perform a ReadNonBlocking on your window every now and then or - # else it won't refresh - # - - for i in range(1, 1000): - window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i / 100), 60), i % 100)) - button, values = window.ReadNonBlocking() - if values is None or button == 'Quit': - break - time.sleep(.01) - else: - window.CloseNonBlocking() - - -What we have here is the same sequence of function calls as in the description. Get a window, add rows to it, show the window, and then refresh it every now and then. - -The new thing in this example is the call use of the Update method for the Text Element. The first thing we do inside the loop is "update" the text element that we made earlier. This changes the value of the text field on the window. The new value will be displayed when `window.ReadNonBlocking()` is called. if you want to have the window reflect your changes immediately, call `window.Refresh()`. - -Note the `else` statement on the for loop. This is needed because we're about to exit the loop while the window is still open. The user has not closed the window using the X nor a button so it's up to the caller to close the window using `CloseNonBlocking`. +Starting in version 2.5 you can change the background colors for the window and the Elements. - +Your windows can go from this: -# Updating Elements (changing elements in active window) - -Persistent windows remain open and thus continue to interact with the user after the Read has returned. Often the program wishes to communicate results (output information) or change an Element's values (such as populating a List Element). - -You can use Update to do things like: -* Have one Element (appear to) make a change to another Element -* Disable a button, slider, input field, etc -* Change a button's text -* Change an Element's text or background color -* Add text to a scrolling output window -* Change the choices in a list -* etc - -The way this is done is via an Update method that is available for nearly all of the Elements. Here is an example of a program that uses a persistent window that is updated. - -![snap0272](https://user-images.githubusercontent.com/13696193/45260249-ec4e4000-b3af-11e8-853b-9b29d0bf7797.jpg) - - -In some programs these updates happen in response to another Element. This program takes a Spinner and a Slider's input values and uses them to resize a Text Element. The Spinner and Slider are on the left, the Text element being changed is on the right. - - - # Testing async window, see if can have a slider - # that adjusts the size of text displayed - - import PySimpleGUI as sg - fontSize = 12 - layout = [[sg.Spin([sz for sz in range(6, 172)], font=('Helvetica 20'), initial_value=fontSize, change_submits=True, key='spin'), - sg.Slider(range=(6,172), orientation='h', size=(10,20), - change_submits=True, key='slider', font=('Helvetica 20')), - sg.Text("Aa", size=(2, 1), font="Helvetica " + str(fontSize), key='text')]] - - sz = fontSize - window = sg.Window("Font size selector", grab_anywhere=False).Layout(layout) - # Event Loop - while True: - button, values= window.Read() - if button is None: - break - sz_spin = int(values['spin']) - sz_slider = int(values['slider']) - sz = sz_spin if sz_spin != fontSize else sz_slider - if sz != fontSize: - fontSize = sz - font = "Helvetica " + str(fontSize) - window.FindElement('text').Update(font=font) - window.FindElement('slider').Update(sz) - window.FindElement('spin').Update(sz) - - print("Done.") - - -Inside the event loop we read the value of the Spinner and the Slider using those Elements' keys. -For example, `values['slider']` is the value of the Slider Element. - -This program changes all 3 elements if either the Slider or the Spinner changes. This is done with these statements: - - window.FindElement('text').Update(font=font) - window.FindElement('slider').Update(sz) - window.FindElement('spin').Update(sz) - -Remember this design pattern because you will use it OFTEN if you use persistent windows. - -It works as follows. The call to `window.FindElement` returns the Element object represented by they provided `key`. This element is then updated by calling it's `Update` method. This is another example of Python's "chaining" feature. We could write this code using the long-form: - - text_element = window.FindElement('text') - text_element.Update(font=font) - -The takeaway from this exercise is that keys are key in PySimpleGUI's design. They are used to both read the values of the window and also to identify elements. As already mentioned, they are used as targets in Button calls. - - ### Updating Multiple Elements - If you have a large number of Elements to update, you can call `Window.UpdateElements()`. - -` UpdateElements(key_list, - value_list)` - -`key_list` - list of keys for elements you wish to update -`value_list` - list of values, one for each key - - window.UpdateElements(('name', 'address', 'phone'), ('Fred Flintstone', '123 Rock Quarry Road', '555#')) - - -# Keyboard & Mouse Capture -Beginning in version 2.10 you can capture keyboard key presses and mouse scroll-wheel events. Keyboard keys can be used, for example, to detect the page-up and page-down keys for a PDF viewer. To use this feature, there's a boolean setting in the Window call `return_keyboard_events` that is set to True in order to get keys returned along with buttons. - -Keys and scroll-wheel events are returned in exactly the same way as buttons. - -For scroll-wheel events, if the mouse is scrolled up, then the `button` text will be `MouseWheel:Up`. For downward scrolling, the text returned is `MouseWheel:Down` - -Keyboard keys return 2 types of key events. For "normal" keys (a,b,c, etc), a single character is returned that represents that key. Modifier and special keys are returned as a string with 2 parts: - - Key Sym:Key Code - -Key Sym is a string such as 'Control_L'. The Key Code is a numeric representation of that key. The left control key, when pressed will return the value 'Control_L:17' - - import PySimpleGUI as sg - - # Recipe for getting keys, one at a time as they are released - # If want to use the space bar, then be sure and disable the "default focus" - - with sg.Window("Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: - text_elem = sg.Text("", size=(18,1)) - layout = [[sg.Text("Press a key or scroll mouse")], - [text_elem], - [sg.Button("OK")]] - - window.Layout(layout) - # ---===--- Loop taking in user input --- # - while True: - button, value = window.ReadNonBlocking() - - if button == "OK" or (button is None and value is None): - print(button, "exiting") - break - if button is not None: - text_elem.Update(button) - -You want to turn off the default focus so that there no buttons that will be selected should you press the spacebar. - -### Realtime Keyboard Capture -Use realtime keyboard capture by calling - - import PySimpleGUI as sg - - with sg.Window("Realtime Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: - layout = [[sg.Text("Hold down a key")], - [sg.Button("OK")]] - - window.Layout(layout) - - while True: - button, value = window.ReadNonBlocking() - - if button == "OK": - print(button, value, "exiting") - break - if button is not None: - print(button) - elif value is None: - break - -# Menus - -Beginning in version 3.01 you can add a menubar to your window. You specify the menus in much the same way as you do window layouts, with lists. Menu selections are returned as button clicks, so be aware of your overall naming conventions. If you have an Exit button and also an Exit menu option, then you won't be able to tell the difference when your window.Read returns. Hopefully will not be a problem. - -This definition: - - menu_def = [['File', ['Open', 'Save', 'Exit',]], - ['Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], - ['Help', 'About...'],] - -Note the placement of ',' and of []. It's tricky to get the nested menus correct that implement cascading menus. See how paste has Special and Normal as a list after it. This means that Paste has a cascading menu with items Special and Normal. - -They menu_def layout produced this window: - -![menu](https://user-images.githubusercontent.com/13696193/45306723-56b7cb00-b4eb-11e8-8cbd-faef0c90f8b4.jpg) - - -You have used ALT-key in other Windows programs to navigate menus. For example Alt-F+X exits the program. The Alt-F pulls down the File menu. The X selects the entry marked Exit. - -The good news is that PySimpleGUI allows you to create the same kind of menus! Your program can play with the big-boys. And, it's trivial to do. - -All that's required is for your to add an "&" in front of the letter you want to appear with an underscore. When you hold the Alt key down you will see the menu with underlines that you marked. - -One other little bit of polish you can add are separators in your list. To add a line in your list of menu choices, create a menu entry that looks like this: ` '---'` - -This is an example Menu with underlines and a separator. - -``` -# ------ Menu Definition ------ # -menu_def = [['&File', ['&Open', '&Save', '---', 'Properties', 'E&xit' ]], - ['&Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], - ['&Help', '&About...'],] -``` - And this is the spiffy menu it produced: - ![menus with shortcuts](https://user-images.githubusercontent.com/13696193/46251674-f5b74f00-c427-11e8-95c6-547adc59041b.jpg) - - +![snap0155](https://user-images.githubusercontent.com/13696193/43273879-a9fdc10a-90cb-11e8-8c20-4f6a244ebe2f.jpg) -# Sample Applications - -Use the example programs as a starting basis for your GUI. Copy, paste, modify and run! The demo files are: - - | Source File| Description | -|--|--| -|**Demo_All_Widgets.py**| Nearly all of the Elements shown in a single window -|**Demo_Borderless_Window.py**| Create clean looking windows with no border -|**Demo_Button_States.py**| One way of implementing disabling of buttons -|**Demo_Calendar.py** | Demo of the Calendar Chooser button -|**Demo_Canvas.py** | window with a Canvas Element that is updated outside of the window -|**Demo_Chat.py** | A chat window with scrollable history -|**Demo_Chatterbot.py** | Front-end to Chatterbot Machine Learning project -|**Demo_Color.py** | How to interact with color using RGB hex values and named colors -|**Demo_Columns.py** | Using the Column Element to create more complex windows -|**Demo_Compare_Files.py** | Using a simple GUI front-end to create a compare 2-files utility -|**Demo_Cookbook_Browser.py** | Source code browser for all Recipes in Cookbook -|**Demo_Dictionary.py** | Specifying and using return values in dictionary format -**Demo_DOC_Viewer_PIL.py** | Display a PDF, HTML, ebook file, etc in your window -|**Demo_DisplayHash1and256.py** | Using high level API and custom window to implement a simple display hash code utility -|**Demo_DuplicateFileFinder.py** | High level API used to get a folder that is used by utility that finds duplicate files. Uses progress meter to show progress. 2 lines of code required to add GUI and meter -|**Demo_Fill_Form.py** | How to perform a bulk-fill for a window. Saving and loading a window from disk -|**Demo Font Sizer.py** | Demonstrates Elements updating other Elements -|**Demo_Func_Callback_Simulator.py** | For the Raspberry Pi crowd. Event loop that simulates traditional GUI callback functions should you already have an architecture that uses them -|**Demo_GoodColors.py** | Using some of the pre-defined PySimpleGUI individual colors -|**Demo_HowDoI.py** | This is a utility to be experienced! It will change how you code -|**Demo_Img_Viewer.py** | Display jpg, png,tiff, bmp files -|**Demo_Keyboard.py** | Using blocking keyboard events -|**Demo_Keyboard_Realtime.py** | Using non-blocking / realtime keyboard events -|**Demo_Machine_Learning.py** | A sample Machine Learning front end -|**Demo_Matplotlib.py** | Integrating with Matplotlib to create a single graph -|**Demo_Matplotlib_Animated.py** | Animated Matplotlib line graph -|**Demo_Matplotlib_Animated_Scatter.py** | Animated Matplotlib scatter graph -|**Demo_Matplotlib_Browser.py** | Browse Matplotlib gallery -|**Demo_Media_Player.py** | Non-blocking window with a media player layout. Demonstrates button graphics, Update method -|**Demo_MIDI_Player.py** | GUI wrapper for Mido MIDI package. Functional MIDI player that controls attached MIDI devices -|**Demo_NonBlocking_Form.py** | a basic async window -|**Demo_OpenCV.py** | Integrated with OpenCV -|**Demo_Password_Login** | Password protection using SHA1 -|**Demo_PDF_Viewer.py** | Submitted by a user! Previews PDF documents. Uses keyboard input & mouse scrollwheel to navigate -|**Demo_Pi_LEDs.py** | Control GPIO using buttons -|**Demo_Pi_Robotics.py** | Simulated robot control using realtime buttons -|**Demo_PNG_Vierwer.py** | Uses Image Element to display PNG files -| **Demo_Progress_Meters.py** | Demonstrates using 2 progress meters simultaneously -|**Demo_Recipes.py** | A collection of various Recipes. Note these are not the same as the Recipes in the Recipe Cookbook -|**Demo_Script_Launcher.py** | Demonstrates one way of adding a front-end onto several command line scripts -|**Demo_Script_Parameters.py** | Add a 1-line GUI to the front of your previously command-line only scripts -|**Demo_Tabbed_Form.py** | Using the Tab feature -|**Demo_Table_Simulation.py** | Use input fields to display and edit tables -|**Demo_Timer.py** | Simple non-blocking window - -## Packages Used In Demos - - - While the core PySimpleGUI code does not utilize any 3rd party packages, some of the demos do. They add a GUI to a few popular packages. These packages include: - * [Chatterbot](https://github.com/gunthercox/ChatterBot) - * [Mido](https://github.com/olemb/mido) - * [Matplotlib](https://matplotlib.org/) - * [PyMuPDF](https://github.com/rk700/PyMuPDF) - - -# Creating a Windows .EXE File - -It's possible to create a single .EXE file that can be distributed to Windows users. There is no requirement to install the Python interpreter on the PC you wish to run it on. Everything it needs is in the one EXE file, assuming you're running a somewhat up to date version of Windows. - -Installation of the packages, you'll need to install PySimpleGUI and PyInstaller (you need to install only once) - -``` -pip install PySimpleGUI -pip install PyInstaller - -``` - -To create your EXE file from your program that uses PySimpleGUI, `my_program.py`, enter this command in your Windows command prompt: - -``` -pyinstaller -wF my_program.py - -``` - -You will be left with a single file, `my_program.exe`, located in a folder named `dist` under the folder where you executed the `pyinstaller` command. - -That's all... Run your `my_program.exe` file on the Windows machine of your choosing. - -> "It's just that easy." - -(famous last words that screw up just about anything being referenced) - -Your EXE file should run without creating a "shell window". Only the GUI window should show up on your taskbar. - -If you get a crash with something like: -``` -ValueError: script '.......\src\tkinter' not found -``` - -Then try adding **`--hidden-import tkinter`** to your command - - - -## Fun Stuff -Here are some things to try if you're bored or want to further customize - -**Debug Output** -Be sure and check out the EasyPrint (Print) function described in the high-level API section. Leave your code the way it is, route your stdout and stderror to a scrolling window. - -For a fun time, add these lines to the top of your script - - import PySimpleGUI as sg - print = sg.Print - -This will turn all of your print statements into prints that display in a window on your screen rather than to the terminal. - -**Look and Feel** -Dial in the look and feel that you like with the `SetOptions` function. You can change all of the defaults in one function call. One line of code to customize the entire GUI. -Or beginning in version 2.9 you can choose from a look and feel using pre-defined color schemes. Call ChangeLookAndFeel with a description string. - - sg.ChangeLookAndFeel('GreenTan') - -Valid values for the description string are: - - GreenTan - LightGreen - BluePurple - Purple - BlueMono - GreenMono - BrownBlue - BrightColors - NeutralBlue - Kayak - SandyBeach - TealMono - -To see the latest list of color choices, take a look at the bottom of the `PySimpleGUI.py` file where you'll find the `ChangLookAndFeel` function. - -You can also combine the `ChangeLookAndFeel` function with the `SetOptions` function to quickly modify one of the canned color schemes. Maybe you like the colors but was more depth to your bezels. You can dial in exactly what you want. - -**ObjToString** -Ever wanted to easily display an objects contents easily? Use ObjToString to get a nicely formatted recursive walk of your objects. -This statement: - - print(sg.ObjToSting(x)) - -And this was the output - - - abc = abc - attr12 = 12 - c = - b = - a = - attr1 = 1 - attr2 = 2 - attr3 = three - attr10 = 10 - attrx = x - -You'll quickly wonder how you ever coded without it. - ---- -# Known Issues -While not an "issue" this is a ***stern warning*** - -## **Do not attempt** to call `PySimpleGUI` from multiple threads! It's `tkinter` based and `tkinter` has issues with multiple threads - -**Progress Meters** - the visual graphic portion of the meter may be off. May return to the native tkinter progress meter solution in the future. Right now a "custom" progress meter is used. On the bright side, the statistics shown are extremely accurate and can tell you something about the performance of your code. If you are running 2 or more progress meters at the same time using `OneLineProgressMeter`, you need to close the meter by using the "Cancel" button rather than the X - -**Async windows** - these include the 'easy' windows (`OneLineProgressMeter` and EasyPrint/Print). If you start overlapping having Async windows open with normal windows then things get a littler squirrelly. Still tracking down the issues and am making it more solid every day possible. You'll know there's an issue when you see blank window. - -**EasyPrint** - EasyPrint is a new feature that's pretty awesome. You print and the output goes to a window, with a scroll bar, that you can copy and paste from. Being a new feature, it's got some potential problems. There are known interaction problems with other GUI windows. For example, closing a Print window can also close other windows you have open. For now, don't close your debug print window until other windows are closed too. - -## Contributing - -A MikeTheWatchGuy production... entirely responsible for this code.... unless it causes you trouble in which case I'm not at all responsible. - -## Versions -|Version | Description | -|--|--| -| 1.0.9 | July 10, 2018 - Initial Release | -| 1.0.21 | July 13, 2018 - Readme updates | -| 2.0.0 | July 16, 2018 - ALL optional parameters renamed from CamelCase to all_lower_case -| 2.1.1 | July 18, 2018 - Global settings exposed, fixes -| 2.2.0| July 20, 2018 - Image Elements, Print output -| 2.3.0 | July 23, 2018 - Changed form.Read return codes, Slider Elements, Listbox element. Renamed some methods but left legacy calls in place for now. -| 2.4.0 | July 24, 2018 - Button images. Fixes so can run on Raspberry Pi -| 2.5.0 | July 26, 2018 - Colors. Listbox scrollbar. tkinter Progress Bar instead of homegrown. -| 2.6.0 | July 27, 2018 - auto_size_button setting. License changed to LGPL 3+ -| 2.7.0 | July 30, 2018 - realtime buttons, window_location default setting -| 2.8.0 | Aug 9, 2018 - New None default option for Checkbox element, text color option for all elements, return values as a dictionary, setting focus, binding return key -| 2.9.0 | Aug 16,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, colored text defaults -| 2.10.0 | Aug 25, 2018 - Keyboard & Mouse features (Return individual keys as if buttons, return mouse scroll-wheel as button, bind return-key to button, control over keyboard focus), SaveAs Button, Update & Get methods for InputText, Update for Listbox, Update & Get for Checkbox, Get for Multiline, Color options for Text Element Update, Progess bar Update can change max value, Update for Button to change text & colors, Update for Image Element, Update for Slider, Form level text justification, Turn off default focus, scroll bar for Listboxes, Images can be from filename or from in-RAM, Update for Image). Fixes - text wrapping in buttons, msg box, removed slider borders entirely and others -| 2.11.0 | Aug 29, 2018 - Lots of little changes that are needed for the demo programs to work. Buttons have their own default element size, fix for Mac default button color, padding support for all elements, option to immediately return if list box gets selected, FilesBrowse button, Canvas Element, Frame Element, Slider resolution option, Form.Refresh method, better text wrapping, 'SystemDefault' look and feel settin -| 2.20.0 | Sept 4, 2018 - Some sizable features this time around of interest to advanced users. Renaming of the MsgBox functions to Popup. Renaming GetFile, etc, to PopupGetFile. High-level windowing capabilities start with Popup, PopupNoWait/PopupNonblocking, PopupNoButtons, default icon, change_submits option for Listbox/Combobox/Slider/Spin/, New OptionMenu element, updating elements after shown, system defaul color option for progress bars, new button type (Dummy Button) that only closes a window, SCROLLABLE Columns!! (yea, playing in the Big League now), LayoutAndShow function removed, form.Fill - bulk updates to forms, FindElement - find element based on key value (ALL elements have keys now), no longer use grid packing for row elements (a potentially huge change), scrolled text box sizing changed, new look and feel themes (Dark, Dark2, Black, Tan, TanBlue, DarkTanBlue, DarkAmber, DarkBlue, Reds, Green) -| 2.30.0 | Sept 6, 2018 - Calendar Chooser (button), borderless windows, load/save form to disk -| 3.0.0 | Sept 7, 2018 - The "fix for poor choice of 2.x numbers" release. Color Chooser (button), "grab anywhere" windows are on by default, disable combo boxes, Input Element text justification (last part needed for 'tables'), Image Element changes to support OpenCV?, PopupGetFile and PopupGetFolder have better no_window option -| 3.01.01 | Sept 10, 2018 - Menus! (sort of a big deal) -| 3.01.02 | Step 11, 2018 - All Element.Update functions have a `disabled` parameter so they can be disabled. Renamed some parameters in Update function (sorry if I broke your code), fix for bug in Image.Update. Wasn't setting size correctly, changed grab_anywhere logic again,added grab anywhere option to PupupGetText (assumes disabled) -| 3.02.00 | Sept 14, 2018 - New Table Element (Beta release), MsgBox removed entirely, font setting for InputText Element, **packing change** risky change that allows some Elements to be resized,removed command parameter from Menu Element, new function names for ReadNonBlocking (Finalize, PreRead), change to text element autosizing and wrapping (yet again), lots of parameter additions to Popup functions (colors, etc). -| 3.03.00 | New feature - One Line Progress Meters, new display_row_numbers for Table Element, fixed bug in EasyProgresssMeters (function will soon go away), OneLine and Easy progress meters set to grab anywhere but can be turned off. -| 03,04.00 | Sept 18, 2018 - New features - Graph Element, Frame Element, more settings exposed to Popup calls. See notes below for more. -| 03.04.01 | Sept 18, 2018 - See release notes -| 03.05.00 | Sept 20, 2018 - See release notes -| 03.05.01 | Sept 22, 2018 - See release notes -| 03.05.02 | Sept 23, 2018 - See release notes -| 03.06.00 | Sept 23, 2018 - Goodbye FlexForm, hello Window -| 03.08.00 | Sept 25, 2018 - Tab and TabGroup Elements\ -| 01.00.00 for 2.7 | Sept 25, 2018 - First release for 2.7 -| 03.08.04 | Sept 30, 2018 - See release notes +to this... with one function call... + + +![snap0156](https://user-images.githubusercontent.com/13696193/43273880-aa1955e6-90cb-11e8-94b6-673ecdb2698c.jpg) + + + +While you can do it on an element by element or window level basis, the easiest way, by far, is a call to `SetOptions`. + +Be aware that once you change these options they are changed for the rest of your program's execution. All of your windows will have that look and feel, until you change it to something else (which could be the system default colors. + +This call sets all of the different color options. + + SetOptions(background_color='#9FB8AD', + text_element_background_color='#9FB8AD', + element_background_color='#9FB8AD', + scrollbar_color=None, + input_elements_background_color='#F7F3EC', + progress_meter_color = ('green', 'blue') + button_color=('white','#475841')) + + + +# Global Settings +**Global Settings** +Let's have some fun customizing! Make PySimpleGUI look the way you want it to look. You can set the global settings using the function `PySimpleGUI.SetOptions`. Each option has an optional parameter that's used to set it. + + SetOptions(icon=None + button_color=(None,None) + element_size=(None,None), + margins=(None,None), + element_padding=(None,None) + auto_size_text=None + auto_size_buttons=None + font=None + border_width=None + slider_border_width=None + slider_relief=None + slider_orientation=None + autoclose_time=None + message_box_line_width=None + progress_meter_border_depth=None + progress_meter_style=None + progress_meter_relief=None + progress_meter_color=None + progress_meter_size=None + text_justification=None + text_color=None + background_color=None + element_background_color=None + text_element_background_color=None + input_elements_background_color=None + element_text_color=None + input_text_color=None + scrollbar_color=None, text_color=None + debug_win_size=(None,None) + window_location=(None,None) + tooltip_time = None + +Explanation of parameters + + icon - filename of icon used for taskbar and title bar + button_color - button color (foreground, background) + element_size - element size (width, height) in characters + margins - tkinter margins around outsize + element_padding - tkinter padding around each element + auto_size_text - autosize the elements to fit their text + auto_size_buttons - autosize the buttons to fit their text + font - font used for elements + border_width - amount of bezel or border around sunken or raised elements + slider_border_width - changes the way sliders look + slider_relief - changes the way sliders look + slider_orientation - changes orientation of slider + autoclose_time - time in seconds for autoclose boxes + message_box_line_width - number of characers in a line of text in message boxes + progress_meter_border_depth - amount of border around raised or lowered progress meters + progress_meter_style - style of progress meter as defined by tkinter + progress_meter_relief - relief style + progress_meter_color - color of the bar and background of progress meters + progress_meter_size - size in (characters, pixels) + background_color - Color of the main window's background + element_background_color - Background color of the elements + text_element_background_color - Text element background color + input_elements_background_color - Input fields background color + element_text_color - Text color of elements that have text, like Radio Buttons + input_text_color - Color of the text that you type in + scrollbar_color - Color for scrollbars (may not always work) + text_color - Text element default text color + text_justification - justification to use on Text Elements. Values are strings - 'left', 'right', 'center' + debug_win_size - size of the Print output window + window_location - location on the screen (x,y) of window's top left cornder + tooltip_time - time in milliseconds to wait before showing a tooltip. Default is 400ms + + +These settings apply to all windows `SetOptions`. The Row options and Element options will take precedence over these settings. Settings can be thought of as levels of settings with the window-level being the highest and the Element-level the lowest. Thus the levels are: + + - window level + - Row level + - Element level + +Each lower level overrides the settings of the higher level. Once settings have been changed, they remain changed for the duration of the program (unless changed again). + +# Persistent windows (Window stays open after button click) + +There are 2 ways to keep a window open after the user has clicked a button. One way is to use non-blocking windows (see the next section). The other way is to use buttons that 'read' the window instead of 'close' the window when clicked. The typical buttons you find in windows, including the shortcut buttons, close the window. These include OK, Cancel, Submit, etc. The Button Element also closes the window. + +The `RButton` Element creates a button that when clicked will return control to the user, but will leave the window open and visible. This button is also used in Non-Blocking windows. The difference is in which call is made to read the window. The `Read` call will block, the `ReadNonBlocking` will not block. + + + +## Asynchronous (Non-Blocking) windows +So you want to be a wizard do ya? Well go boldly! + +Use async windows sparingly. It's possible to have a window that appears to be async, but it is not. **Please** try to find other methods before going to async windows. The reason for this plea is that async windows poll tkinter over and over. If you do not have a sleep in your loop, you will eat up 100% of the CPU time. + +When to use a non-blocking window: +* A media file player like an MP3 player +* A status dashboard that's periodically updated +* Progress Meters - when you want to make your own progress meters +* Output using print to a scrolled text element. Good for debugging. + +If your application doesn't follow the basic design pattern at one of those, then it shouldn't be executed as a non-blocking window. + + +### Instead of ReadNonBlocking --- Use `change_submits = True` or return_keyboard_events = True + +Any time you are thinking "I want an X Element to cause a Y Element to do something", then you want to use the `change_submits` option. + +***Instead of polling, try options that cause the window to return to you.*** By using non-blocking windows, you are *polling*. You can indeed create your application by polling. It will work. But you're going to be maxing out your processor and may even take longer to react to an event than if you used another technique. + +**Examples** + +One example is you have an input field that changes as you press buttons on an on-screen keypad. + +![keypad 3](https://user-images.githubusercontent.com/13696193/45260275-a2198e80-b3b0-11e8-85fe-a4ce6484510f.jpg) + + + + +### Periodically Calling`ReadNonBlocking` + +Periodically "refreshing" the visible GUI. The longer you wait between updates to your GUI the more sluggish your windows will feel. It is up to you to make these calls or your GUI will freeze. + +There are 2 methods of interacting with non-blocking windows. +1. Read the window just as you would a normal window +2. "Refresh" the window's values without reading the window. It's a quick operation meant to show the user the latest values + + With asynchronous windows the window is shown, user input is read, but your code keeps right on chugging. YOUR responsibility is to call `PySimpleGUI.ReadNonBlocking` on a periodic basis. Once a second or more will produce a reasonably snappy GUI. + + #### Exiting a Non-Blocking window + +It's important to always provide a "way out" for your user. Make sure you have provided a button or some other mechanism to exit. Also be sure to check for closed windows in your code. It is possible for a window to look closed, but continue running your event loop. + +Typically when reading a window you check `if Button is None` to determine if a window was closed. With NonBlocking windows, buttons will be None unless a button or a key was returned. The way you determine if a window was closed in a non-blocking window is to check **both** the button and the values are None. Since button is normally None, you only need to test for `value is None` in your code. + +The proper code to check if the user has exited the window will be a polling-loop that looks something like this: + + while True: + event, values = window.ReadNonBlocking() + if values is None or event == 'Quit': + break + + +We're going to build an app that does the latter. It's going to update our window with a running clock. + +The basic flow and functions you will be calling are: +Setup + + window = Window() + window_rows = ..... + window.Layout(window_rows, non_blocking=True).Read() + + +Periodic refresh + + window.ReadNonBlocking() or window.Refresh() + +If you need to close the window + + window.CloseNonBlocking() + +Rather than the usual `window.Layout().Read()` call, we're manually adding the rows (doing the layout) and then showing the window. After the window is shown, you simply call `window.ReadNonBlocking()` every now and then. + +When you are ready to close the window (assuming the window wasn't closed by the user or a button click) you simply call `window.CloseNonBlocking()` + +**Example - Running timer that updates** +See the sample code on the GitHub named Demo Media Player for another example of Async windows. We're going to make a window and update one of the elements of that window every .01 seconds. Here's the entire code to do that. + + + import PySimpleGUI as sg + import time + + # window that doesn't block + # Make a window, but don't use context manager + window = sg.Window('Running Timer', auto_size_text=True) + + # Create the layout + window_rows = [[sg.Text('Non-blocking GUI with updates')], + [sg.Text('', size=(8, 2), font=('Helvetica', 20), key='output') ], + [sg.Button('Quit')]] + # Layout the rows of the window and perform a read. Indicate the window is non-blocking! + window.Layout(window_rows).ReadNonBlocking() + + # + # Some place later in your code... + # You need to perform a ReadNonBlocking on your window every now and then or + # else it won't refresh + # + + for i in range(1, 1000): + window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i / 100), 60), i % 100)) + event, values = window.ReadNonBlocking() + if values is None or event == 'Quit': + break + time.sleep(.01) + else: + window.CloseNonBlocking() + + +What we have here is the same sequence of function calls as in the description. Get a window, add rows to it, show the window, and then refresh it every now and then. + +The new thing in this example is the call use of the Update method for the Text Element. The first thing we do inside the loop is "update" the text element that we made earlier. This changes the value of the text field on the window. The new value will be displayed when `window.ReadNonBlocking()` is called. if you want to have the window reflect your changes immediately, call `window.Refresh()`. + +Note the `else` statement on the for loop. This is needed because we're about to exit the loop while the window is still open. The user has not closed the window using the X nor a button so it's up to the caller to close the window using `CloseNonBlocking`. + + + +# Updating Elements (changing elements in active window) + +Persistent windows remain open and thus continue to interact with the user after the Read has returned. Often the program wishes to communicate results (output information) or change an Element's values (such as populating a List Element). + +You can use Update to do things like: +* Have one Element (appear to) make a change to another Element +* Disable a button, slider, input field, etc +* Change a button's text +* Change an Element's text or background color +* Add text to a scrolling output window +* Change the choices in a list +* etc + +The way this is done is via an Update method that is available for nearly all of the Elements. Here is an example of a program that uses a persistent window that is updated. + +![snap0272](https://user-images.githubusercontent.com/13696193/45260249-ec4e4000-b3af-11e8-853b-9b29d0bf7797.jpg) + + +In some programs these updates happen in response to another Element. This program takes a Spinner and a Slider's input values and uses them to resize a Text Element. The Spinner and Slider are on the left, the Text element being changed is on the right. + + + # Testing async window, see if can have a slider + # that adjusts the size of text displayed + + import PySimpleGUI as sg + fontSize = 12 + layout = [[sg.Spin([sz for sz in range(6, 172)], font=('Helvetica 20'), initial_value=fontSize, change_submits=True, key='spin'), + sg.Slider(range=(6,172), orientation='h', size=(10,20), + change_submits=True, key='slider', font=('Helvetica 20')), + sg.Text("Aa", size=(2, 1), font="Helvetica " + str(fontSize), key='text')]] + + sz = fontSize + window = sg.Window("Font size selector", grab_anywhere=False).Layout(layout) + # Event Loop + while True: + event, values= window.Read() + if event is None: + break + sz_spin = int(values['spin']) + sz_slider = int(values['slider']) + sz = sz_spin if sz_spin != fontSize else sz_slider + if sz != fontSize: + fontSize = sz + font = "Helvetica " + str(fontSize) + window.FindElement('text').Update(font=font) + window.FindElement('slider').Update(sz) + window.FindElement('spin').Update(sz) + + print("Done.") + + +Inside the event loop we read the value of the Spinner and the Slider using those Elements' keys. +For example, `values['slider']` is the value of the Slider Element. + +This program changes all 3 elements if either the Slider or the Spinner changes. This is done with these statements: + + window.FindElement('text').Update(font=font) + window.FindElement('slider').Update(sz) + window.FindElement('spin').Update(sz) + +Remember this design pattern because you will use it OFTEN if you use persistent windows. + +It works as follows. The call to `window.FindElement` returns the Element object represented by they provided `key`. This element is then updated by calling it's `Update` method. This is another example of Python's "chaining" feature. We could write this code using the long-form: + + text_element = window.FindElement('text') + text_element.Update(font=font) + +The takeaway from this exercise is that keys are key in PySimpleGUI's design. They are used to both read the values of the window and also to identify elements. As already mentioned, they are used as targets in Button calls. + + ### Updating Multiple Elements + If you have a large number of Elements to update, you can call `Window.UpdateElements()`. + +` UpdateElements(key_list, + value_list)` + +`key_list` - list of keys for elements you wish to update +`value_list` - list of values, one for each key + + window.UpdateElements(('name', 'address', 'phone'), ('Fred Flintstone', '123 Rock Quarry Road', '555#')) + + +# Keyboard & Mouse Capture +Beginning in version 2.10 you can capture keyboard key presses and mouse scroll-wheel events. Keyboard keys can be used, for example, to detect the page-up and page-down keys for a PDF viewer. To use this feature, there's a boolean setting in the Window call `return_keyboard_events` that is set to True in order to get keys returned along with buttons. + +Keys and scroll-wheel events are returned in exactly the same way as buttons. + +For scroll-wheel events, if the mouse is scrolled up, then the `button` text will be `MouseWheel:Up`. For downward scrolling, the text returned is `MouseWheel:Down` + +Keyboard keys return 2 types of key events. For "normal" keys (a,b,c, etc), a single character is returned that represents that key. Modifier and special keys are returned as a string with 2 parts: + + Key Sym:Key Code + +Key Sym is a string such as 'Control_L'. The Key Code is a numeric representation of that key. The left control key, when pressed will return the value 'Control_L:17' + + import PySimpleGUI as sg + + # Recipe for getting keys, one at a time as they are released + # If want to use the space bar, then be sure and disable the "default focus" + + with sg.Window("Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: + text_elem = sg.Text("", size=(18,1)) + layout = [[sg.Text("Press a key or scroll mouse")], + [text_elem], + [sg.Button("OK")]] + + window.Layout(layout) + # ---===--- Loop taking in user input --- # + while True: + event, value = window.ReadNonBlocking() + + if event == "OK" or (event is None and value is None): + print(event, "exiting") + break + if eventis not None: + text_elem.Update(event) + +You want to turn off the default focus so that there no buttons that will be selected should you press the spacebar. + +### Realtime Keyboard Capture +Use realtime keyboard capture by calling + + import PySimpleGUI as sg + + with sg.Window("Realtime Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: + layout = [[sg.Text("Hold down a key")], + [sg.Button("OK")]] + + window.Layout(layout) + + while True: + event, value = window.ReadNonBlocking() + + if event == "OK": + print(event, value, "exiting") + break + if eventis not None: + print(event) + elif value is None: + break + +# Menus + +Beginning in version 3.01 you can add a menubar to your window. You specify the menus in much the same way as you do window layouts, with lists. Menu selections are returned as button clicks, so be aware of your overall naming conventions. If you have an Exit button and also an Exit menu option, then you won't be able to tell the difference when your window.Read returns. Hopefully will not be a problem. + +This definition: + + menu_def = [['File', ['Open', 'Save', 'Exit',]], + ['Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], + ['Help', 'About...'],] + +Note the placement of ',' and of []. It's tricky to get the nested menus correct that implement cascading menus. See how paste has Special and Normal as a list after it. This means that Paste has a cascading menu with items Special and Normal. + +They menu_def layout produced this window: + +![menu](https://user-images.githubusercontent.com/13696193/45306723-56b7cb00-b4eb-11e8-8cbd-faef0c90f8b4.jpg) + + +You have used ALT-key in other Windows programs to navigate menus. For example Alt-F+X exits the program. The Alt-F pulls down the File menu. The X selects the entry marked Exit. + +The good news is that PySimpleGUI allows you to create the same kind of menus! Your program can play with the big-boys. And, it's trivial to do. + +All that's required is for your to add an "&" in front of the letter you want to appear with an underscore. When you hold the Alt key down you will see the menu with underlines that you marked. + +One other little bit of polish you can add are separators in your list. To add a line in your list of menu choices, create a menu entry that looks like this: ` '---'` + +This is an example Menu with underlines and a separator. + +``` +# ------ Menu Definition ------ # +menu_def = [['&File', ['&Open', '&Save', '---', 'Properties', 'E&xit' ]], + ['&Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], + ['&Help', '&About...'],] +``` + And this is the spiffy menu it produced: + ![menus with shortcuts](https://user-images.githubusercontent.com/13696193/46251674-f5b74f00-c427-11e8-95c6-547adc59041b.jpg) + + + + +# Sample Applications + +Use the example programs as a starting basis for your GUI. Copy, paste, modify and run! The demo files are: + + | Source File| Description | +|--|--| +|**Demo_All_Widgets.py**| Nearly all of the Elements shown in a single window +|**Demo_Borderless_Window.py**| Create clean looking windows with no border +|**Demo_Button_States.py**| One way of implementing disabling of buttons +|**Demo_Calendar.py** | Demo of the Calendar Chooser button +|**Demo_Canvas.py** | window with a Canvas Element that is updated outside of the window +|**Demo_Chat.py** | A chat window with scrollable history +|**Demo_Chatterbot.py** | Front-end to Chatterbot Machine Learning project +|**Demo_Color.py** | How to interact with color using RGB hex values and named colors +|**Demo_Columns.py** | Using the Column Element to create more complex windows +|**Demo_Compare_Files.py** | Using a simple GUI front-end to create a compare 2-files utility +|**Demo_Cookbook_Browser.py** | Source code browser for all Recipes in Cookbook +|**Demo_Dictionary.py** | Specifying and using return values in dictionary format +**Demo_DOC_Viewer_PIL.py** | Display a PDF, HTML, ebook file, etc in your window +|**Demo_DisplayHash1and256.py** | Using high level API and custom window to implement a simple display hash code utility +|**Demo_DuplicateFileFinder.py** | High level API used to get a folder that is used by utility that finds duplicate files. Uses progress meter to show progress. 2 lines of code required to add GUI and meter +|**Demo_Fill_Form.py** | How to perform a bulk-fill for a window. Saving and loading a window from disk +|**Demo Font Sizer.py** | Demonstrates Elements updating other Elements +|**Demo_Func_Callback_Simulator.py** | For the Raspberry Pi crowd. Event loop that simulates traditional GUI callback functions should you already have an architecture that uses them +|**Demo_GoodColors.py** | Using some of the pre-defined PySimpleGUI individual colors +|**Demo_HowDoI.py** | This is a utility to be experienced! It will change how you code +|**Demo_Img_Viewer.py** | Display jpg, png,tiff, bmp files +|**Demo_Keyboard.py** | Using blocking keyboard events +|**Demo_Keyboard_Realtime.py** | Using non-blocking / realtime keyboard events +|**Demo_Machine_Learning.py** | A sample Machine Learning front end +|**Demo_Matplotlib.py** | Integrating with Matplotlib to create a single graph +|**Demo_Matplotlib_Animated.py** | Animated Matplotlib line graph +|**Demo_Matplotlib_Animated_Scatter.py** | Animated Matplotlib scatter graph +|**Demo_Matplotlib_Browser.py** | Browse Matplotlib gallery +|**Demo_Media_Player.py** | Non-blocking window with a media player layout. Demonstrates button graphics, Update method +|**Demo_MIDI_Player.py** | GUI wrapper for Mido MIDI package. Functional MIDI player that controls attached MIDI devices +|**Demo_NonBlocking_Form.py** | a basic async window +|**Demo_OpenCV.py** | Integrated with OpenCV +|**Demo_Password_Login** | Password protection using SHA1 +|**Demo_PDF_Viewer.py** | Submitted by a user! Previews PDF documents. Uses keyboard input & mouse scrollwheel to navigate +|**Demo_Pi_LEDs.py** | Control GPIO using buttons +|**Demo_Pi_Robotics.py** | Simulated robot control using realtime buttons +|**Demo_PNG_Vierwer.py** | Uses Image Element to display PNG files +| **Demo_Progress_Meters.py** | Demonstrates using 2 progress meters simultaneously +|**Demo_Recipes.py** | A collection of various Recipes. Note these are not the same as the Recipes in the Recipe Cookbook +|**Demo_Script_Launcher.py** | Demonstrates one way of adding a front-end onto several command line scripts +|**Demo_Script_Parameters.py** | Add a 1-line GUI to the front of your previously command-line only scripts +|**Demo_Tabbed_Form.py** | Using the Tab feature +|**Demo_Table_Simulation.py** | Use input fields to display and edit tables +|**Demo_Timer.py** | Simple non-blocking window + +## Packages Used In Demos + + + While the core PySimpleGUI code does not utilize any 3rd party packages, some of the demos do. They add a GUI to a few popular packages. These packages include: + * [Chatterbot](https://github.com/gunthercox/ChatterBot) + * [Mido](https://github.com/olemb/mido) + * [Matplotlib](https://matplotlib.org/) + * [PyMuPDF](https://github.com/rk700/PyMuPDF) + + +# Creating a Windows .EXE File + +It's possible to create a single .EXE file that can be distributed to Windows users. There is no requirement to install the Python interpreter on the PC you wish to run it on. Everything it needs is in the one EXE file, assuming you're running a somewhat up to date version of Windows. + +Installation of the packages, you'll need to install PySimpleGUI and PyInstaller (you need to install only once) + +``` +pip install PySimpleGUI +pip install PyInstaller + +``` + +To create your EXE file from your program that uses PySimpleGUI, `my_program.py`, enter this command in your Windows command prompt: + +``` +pyinstaller -wF my_program.py + +``` + +You will be left with a single file, `my_program.exe`, located in a folder named `dist` under the folder where you executed the `pyinstaller` command. + +That's all... Run your `my_program.exe` file on the Windows machine of your choosing. + +> "It's just that easy." + +(famous last words that screw up just about anything being referenced) + +Your EXE file should run without creating a "shell window". Only the GUI window should show up on your taskbar. + +If you get a crash with something like: +``` +ValueError: script '.......\src\tkinter' not found +``` + +Then try adding **`--hidden-import tkinter`** to your command + + + +## Fun Stuff +Here are some things to try if you're bored or want to further customize + +**Debug Output** +Be sure and check out the EasyPrint (Print) function described in the high-level API section. Leave your code the way it is, route your stdout and stderror to a scrolling window. + +For a fun time, add these lines to the top of your script + + import PySimpleGUI as sg + print = sg.Print + +This will turn all of your print statements into prints that display in a window on your screen rather than to the terminal. + +**Look and Feel** +Dial in the look and feel that you like with the `SetOptions` function. You can change all of the defaults in one function call. One line of code to customize the entire GUI. +Or beginning in version 2.9 you can choose from a look and feel using pre-defined color schemes. Call ChangeLookAndFeel with a description string. + + sg.ChangeLookAndFeel('GreenTan') + +Valid values for the description string are: + + GreenTan + LightGreen + BluePurple + Purple + BlueMono + GreenMono + BrownBlue + BrightColors + NeutralBlue + Kayak + SandyBeach + TealMono + +To see the latest list of color choices, take a look at the bottom of the `PySimpleGUI.py` file where you'll find the `ChangLookAndFeel` function. + +You can also combine the `ChangeLookAndFeel` function with the `SetOptions` function to quickly modify one of the canned color schemes. Maybe you like the colors but was more depth to your bezels. You can dial in exactly what you want. + +**ObjToString** +Ever wanted to easily display an objects contents easily? Use ObjToString to get a nicely formatted recursive walk of your objects. +This statement: + + print(sg.ObjToSting(x)) + +And this was the output + + + abc = abc + attr12 = 12 + c = + b = + a = + attr1 = 1 + attr2 = 2 + attr3 = three + attr10 = 10 + attrx = x + +You'll quickly wonder how you ever coded without it. + +--- +# Known Issues +While not an "issue" this is a ***stern warning*** + +## **Do not attempt** to call `PySimpleGUI` from multiple threads! It's `tkinter` based and `tkinter` has issues with multiple threads + +**Progress Meters** - the visual graphic portion of the meter may be off. May return to the native tkinter progress meter solution in the future. Right now a "custom" progress meter is used. On the bright side, the statistics shown are extremely accurate and can tell you something about the performance of your code. If you are running 2 or more progress meters at the same time using `OneLineProgressMeter`, you need to close the meter by using the "Cancel" button rather than the X + +**Async windows** - these include the 'easy' windows (`OneLineProgressMeter` and EasyPrint/Print). If you start overlapping having Async windows open with normal windows then things get a littler squirrelly. Still tracking down the issues and am making it more solid every day possible. You'll know there's an issue when you see blank window. + +**EasyPrint** - EasyPrint is a new feature that's pretty awesome. You print and the output goes to a window, with a scroll bar, that you can copy and paste from. Being a new feature, it's got some potential problems. There are known interaction problems with other GUI windows. For example, closing a Print window can also close other windows you have open. For now, don't close your debug print window until other windows are closed too. + +## Contributing + +A MikeTheWatchGuy production... entirely responsible for this code.... unless it causes you trouble in which case I'm not at all responsible. + +## Versions +|Version | Description | +|--|--| +| 1.0.9 | July 10, 2018 - Initial Release | +| 1.0.21 | July 13, 2018 - Readme updates | +| 2.0.0 | July 16, 2018 - ALL optional parameters renamed from CamelCase to all_lower_case +| 2.1.1 | July 18, 2018 - Global settings exposed, fixes +| 2.2.0| July 20, 2018 - Image Elements, Print output +| 2.3.0 | July 23, 2018 - Changed form.Read return codes, Slider Elements, Listbox element. Renamed some methods but left legacy calls in place for now. +| 2.4.0 | July 24, 2018 - Button images. Fixes so can run on Raspberry Pi +| 2.5.0 | July 26, 2018 - Colors. Listbox scrollbar. tkinter Progress Bar instead of homegrown. +| 2.6.0 | July 27, 2018 - auto_size_button setting. License changed to LGPL 3+ +| 2.7.0 | July 30, 2018 - realtime buttons, window_location default setting +| 2.8.0 | Aug 9, 2018 - New None default option for Checkbox element, text color option for all elements, return values as a dictionary, setting focus, binding return key +| 2.9.0 | Aug 16,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, colored text defaults +| 2.10.0 | Aug 25, 2018 - Keyboard & Mouse features (Return individual keys as if buttons, return mouse scroll-wheel as button, bind return-key to button, control over keyboard focus), SaveAs Button, Update & Get methods for InputText, Update for Listbox, Update & Get for Checkbox, Get for Multiline, Color options for Text Element Update, Progess bar Update can change max value, Update for Button to change text & colors, Update for Image Element, Update for Slider, Form level text justification, Turn off default focus, scroll bar for Listboxes, Images can be from filename or from in-RAM, Update for Image). Fixes - text wrapping in buttons, msg box, removed slider borders entirely and others +| 2.11.0 | Aug 29, 2018 - Lots of little changes that are needed for the demo programs to work. Buttons have their own default element size, fix for Mac default button color, padding support for all elements, option to immediately return if list box gets selected, FilesBrowse button, Canvas Element, Frame Element, Slider resolution option, Form.Refresh method, better text wrapping, 'SystemDefault' look and feel settin +| 2.20.0 | Sept 4, 2018 - Some sizable features this time around of interest to advanced users. Renaming of the MsgBox functions to Popup. Renaming GetFile, etc, to PopupGetFile. High-level windowing capabilities start with Popup, PopupNoWait/PopupNonblocking, PopupNoButtons, default icon, change_submits option for Listbox/Combobox/Slider/Spin/, New OptionMenu element, updating elements after shown, system defaul color option for progress bars, new button type (Dummy Button) that only closes a window, SCROLLABLE Columns!! (yea, playing in the Big League now), LayoutAndShow function removed, form.Fill - bulk updates to forms, FindElement - find element based on key value (ALL elements have keys now), no longer use grid packing for row elements (a potentially huge change), scrolled text box sizing changed, new look and feel themes (Dark, Dark2, Black, Tan, TanBlue, DarkTanBlue, DarkAmber, DarkBlue, Reds, Green) +| 2.30.0 | Sept 6, 2018 - Calendar Chooser (button), borderless windows, load/save form to disk +| 3.0.0 | Sept 7, 2018 - The "fix for poor choice of 2.x numbers" release. Color Chooser (button), "grab anywhere" windows are on by default, disable combo boxes, Input Element text justification (last part needed for 'tables'), Image Element changes to support OpenCV?, PopupGetFile and PopupGetFolder have better no_window option +| 3.01.01 | Sept 10, 2018 - Menus! (sort of a big deal) +| 3.01.02 | Step 11, 2018 - All Element.Update functions have a `disabled` parameter so they can be disabled. Renamed some parameters in Update function (sorry if I broke your code), fix for bug in Image.Update. Wasn't setting size correctly, changed grab_anywhere logic again,added grab anywhere option to PupupGetText (assumes disabled) +| 3.02.00 | Sept 14, 2018 - New Table Element (Beta release), MsgBox removed entirely, font setting for InputText Element, **packing change** risky change that allows some Elements to be resized,removed command parameter from Menu Element, new function names for ReadNonBlocking (Finalize, PreRead), change to text element autosizing and wrapping (yet again), lots of parameter additions to Popup functions (colors, etc). +| 3.03.00 | New feature - One Line Progress Meters, new display_row_numbers for Table Element, fixed bug in EasyProgresssMeters (function will soon go away), OneLine and Easy progress meters set to grab anywhere but can be turned off. +| 03,04.00 | Sept 18, 2018 - New features - Graph Element, Frame Element, more settings exposed to Popup calls. See notes below for more. +| 03.04.01 | Sept 18, 2018 - See release notes +| 03.05.00 | Sept 20, 2018 - See release notes +| 03.05.01 | Sept 22, 2018 - See release notes +| 03.05.02 | Sept 23, 2018 - See release notes +| 03.06.00 | Sept 23, 2018 - Goodbye FlexForm, hello Window +| 03.08.00 | Sept 25, 2018 - Tab and TabGroup Elements\ +| 01.00.00 for 2.7 | Sept 25, 2018 - First release for 2.7 +| 03.08.04 | Sept 30, 2018 - See release notes | 03.09.00 | Oct 1, 2018 | | 2.7 01.01.00 | Oct 1, 2018 | 2.7 01.01.02 | Oct 8, 2018 | 03.09.01 | Oct 8, 2018 | 3.9.3 & 1.1.3 | Oct 11, 2018 - - -## Release Notes -2.3 - Sliders, Listbox's and Image elements (oh my!) - -If using Progress Meters, avoid cancelling them when you have another window open. It could lead to future windows being blank. It's being worked on. - -New debug printing capability. `sg.Print` - -2.5 Discovered issue with scroll bar on `Output` elements. The bar will match size of ROW not the size of the element. Normally you never notice this due to where on a form the `Output` element goes. - -Listboxes are still without scrollwheels. The mouse can drag to see more items. The mouse scrollwheel will also scroll the list and will `page up` and `page down` keys. - -2.7 Is the "feature complete" release. Pretty much all features are done and in the code - -2.8 More text color controls. The caller has more control over things like the focus and what buttons should be clicked when enter key is pressed. Return values as a dictionary! (NICE addition) - -2.9 COLUMNS! This is the biggest feature and had the biggest impact on the code base. It was a difficult feature to add, but it was worth it. Can now make even more layouts. Almost any layout is possible with this addition. - -.................. insert releases 2.9 to 2.30 ................. - -3.0 We've come a long way baby! Time for a major revision bump. One reason is that the numbers started to confuse people the latest release was 2.30, but some people read it as 2.3 and thought it went backwards. I kinda messed up the 2.x series of numbers, so why not start with a clean slate. A lot has happened anyway so it's well earned. - -One change that will set PySimpleGUI apart is the parlor trick of being able to move the window by clicking on it anywhere. This is turned on by default. It's not a common way to interact with windows. Normally you have to move using the titlebar. Not so with PySimpleGUI. Now you can drag using any part of the window. You will want to turn this off for windows with sliders. This feature is enabled in the Window call. - -Related to the Grab Anywhere feature is the no_titlebar option, again found in the call to Window. Your window will be a spiffy, borderless window. It's a really interesting effect. Slight problem is that you do not have an icon on the taskbar with these types of windows, so if you don't supply a button to close the window, there's no way to close it other than task manager. - -3.0.2 Still making changes to Update methods with many more ahead in the future. Continue to mess with grab anywhere option. Needed to disable in more places such as the PopupGetText function. Any time these is text input on a form, you generally want to turn off the grab anywhere feature. - -#### 3.2.0 - Biggest change was the addition of the Table Element. Trying to make changes so that form resizing is a possibility but unknown if will work in the long run. Removed all MsgBox, Get* functions and replaced with Popup functions. Popups had multiple new parameters added to change the look and feel of a popup. - -#### 3.3.0 -OneLineProgressMeter function added which gives you not only a one-line solution to progress meters, but it also gives you the ability to have more than 1 running at the same time, something not possible with the EasyProgressMeterCall - -#### 3.4.0 - -* Frame - New Element - a labelled frame for grouping elements. Similar - to Column -* Graph (like a Canvas element except uses the caller's - coordinate system rather than tkinter's). -* initial_folder - sets starting folder for browsing type buttons (browse for file/folder). -* Buttons return key value rather than button text **If** a `key` is specified, -* - OneLineProgressMeter! Replaced EasyProgressMeter (sorry folks that's - the way progress works sometimes) - * Popup - changed ALL of the Popup calls to provide many more customization settings - * Popup - * PopupGetFolder - * PopupGetFile - * PopupGetText - * Popup - * PopupNoButtons - * PopupNonBlocking - * PopupNoTitlebar - * PopupAutoClose - * PopupCancel - * PopupOK - * PopupOKCancel - * PopupYesNo - -#### 3.4.1 -* Button.GetText - Button class method. Returns the current text being shown on a button. -* Menu - Tearoff option. Determines if menus should allow them to be torn off -* Help - Shorcut button. Like Submit, cancel, etc -* ReadButton - shortcut for ReadFormButton - -#### 3.5.0 -* Tool Tips for all elements -* Clickable text -* Text Element relief setting -* Keys as targets for buttons -* New names for buttons: - * Button = SimpleButton - * RButton = ReadButton = ReadFormButton -* Double clickable list entries -* Auto sizing table widths works now -* Feature DELETED - Scaling. Removed from all elements - -#### 3.5.1 -* Bug fix for broken PySimpleGUI if Python version < 3.6 (sorry!) -* LOTS of Readme changes - -#### 3.5.2 -* Made `Finalize()` in a way that it can be chained -* Fixed bug in return values from Frame Element contents - -#### 3.6.0 -* Renamed FlexForm to Window -* Removed LookAndFeel capability from Mac platform. - -#### 3.8.0 -* Tab and TabGroup Elements - awesome new capabilities - -#### 1.0.0 Python 2.7 -It's official. There is a 2.7 version of PySimpleGUI! - -#### 3.8.2 -* Exposed `TKOut` in Output Element -* `DrawText` added to Graph Elements -* Removed `Window.UpdateElements` -* `Window.grab_anywere` defaults to False - -#### 3.8.3 -* Listbox, Slider, Combobox, Checkbox, Spin, Tab Group - if change_submits is set, will return the Element's key rather than '' -* Added change_submits capability to Checkbox, Tab Group -* Combobox - Can set value to an Index into the Values table rather than the Value itself -* Warnings added to Drawing routines for Graph element (rather than crashing) -* Window - can "force top level" window to be used rather than a normal window. Means that instead of calling Tk to get a window, will call TopLevel to get the window -* Window Disable / Enable - Disables events (button clicks, etc) for a Window. Use this when you open a second window and want to disable the first window from doing anything. This will simulate a 'dialog box' -* Tab Group returns a value with Window is Read. Return value is the string of the selected tab -* Turned off grab_anywhere for Popups -* New parameter, default_extension, for PopupGetFile -* Keyboard shortcuts for menu items. Can hold ALT key to select items in men -* Removed old-style Tabs - Risky change because it hit fundamental window packing and creation. Will also break any old code using this style tab (sorry folks this is how progress happens) - -#### 3.8.6 - -* Fix for Menus. -* Fixed table colors. Now they work -* Fixed returning keys for tabs -* Window Hide / UnHide methods -* Changed all Popups to remove context manager -* Error checking for Graphing objects and for Element Updates + + +## Release Notes +2.3 - Sliders, Listbox's and Image elements (oh my!) + +If using Progress Meters, avoid cancelling them when you have another window open. It could lead to future windows being blank. It's being worked on. + +New debug printing capability. `sg.Print` + +2.5 Discovered issue with scroll bar on `Output` elements. The bar will match size of ROW not the size of the element. Normally you never notice this due to where on a form the `Output` element goes. + +Listboxes are still without scrollwheels. The mouse can drag to see more items. The mouse scrollwheel will also scroll the list and will `page up` and `page down` keys. + +2.7 Is the "feature complete" release. Pretty much all features are done and in the code + +2.8 More text color controls. The caller has more control over things like the focus and what buttons should be clicked when enter key is pressed. Return values as a dictionary! (NICE addition) + +2.9 COLUMNS! This is the biggest feature and had the biggest impact on the code base. It was a difficult feature to add, but it was worth it. Can now make even more layouts. Almost any layout is possible with this addition. + +.................. insert releases 2.9 to 2.30 ................. + +3.0 We've come a long way baby! Time for a major revision bump. One reason is that the numbers started to confuse people the latest release was 2.30, but some people read it as 2.3 and thought it went backwards. I kinda messed up the 2.x series of numbers, so why not start with a clean slate. A lot has happened anyway so it's well earned. + +One change that will set PySimpleGUI apart is the parlor trick of being able to move the window by clicking on it anywhere. This is turned on by default. It's not a common way to interact with windows. Normally you have to move using the titlebar. Not so with PySimpleGUI. Now you can drag using any part of the window. You will want to turn this off for windows with sliders. This feature is enabled in the Window call. + +Related to the Grab Anywhere feature is the no_titlebar option, again found in the call to Window. Your window will be a spiffy, borderless window. It's a really interesting effect. Slight problem is that you do not have an icon on the taskbar with these types of windows, so if you don't supply a button to close the window, there's no way to close it other than task manager. + +3.0.2 Still making changes to Update methods with many more ahead in the future. Continue to mess with grab anywhere option. Needed to disable in more places such as the PopupGetText function. Any time these is text input on a form, you generally want to turn off the grab anywhere feature. + +#### 3.2.0 + Biggest change was the addition of the Table Element. Trying to make changes so that form resizing is a possibility but unknown if will work in the long run. Removed all MsgBox, Get* functions and replaced with Popup functions. Popups had multiple new parameters added to change the look and feel of a popup. + +#### 3.3.0 +OneLineProgressMeter function added which gives you not only a one-line solution to progress meters, but it also gives you the ability to have more than 1 running at the same time, something not possible with the EasyProgressMeterCall + +#### 3.4.0 + +* Frame - New Element - a labelled frame for grouping elements. Similar + to Column +* Graph (like a Canvas element except uses the caller's + coordinate system rather than tkinter's). +* initial_folder - sets starting folder for browsing type buttons (browse for file/folder). +* Buttons return key value rather than button text **If** a `key` is specified, +* + OneLineProgressMeter! Replaced EasyProgressMeter (sorry folks that's + the way progress works sometimes) + * Popup - changed ALL of the Popup calls to provide many more customization settings + * Popup + * PopupGetFolder + * PopupGetFile + * PopupGetText + * Popup + * PopupNoButtons + * PopupNonBlocking + * PopupNoTitlebar + * PopupAutoClose + * PopupCancel + * PopupOK + * PopupOKCancel + * PopupYesNo + +#### 3.4.1 +* Button.GetText - Button class method. Returns the current text being shown on a button. +* Menu - Tearoff option. Determines if menus should allow them to be torn off +* Help - Shorcut button. Like Submit, cancel, etc +* ReadButton - shortcut for ReadFormButton + +#### 3.5.0 +* Tool Tips for all elements +* Clickable text +* Text Element relief setting +* Keys as targets for buttons +* New names for buttons: + * Button = SimpleButton + * RButton = ReadButton = ReadFormButton +* Double clickable list entries +* Auto sizing table widths works now +* Feature DELETED - Scaling. Removed from all elements + +#### 3.5.1 +* Bug fix for broken PySimpleGUI if Python version < 3.6 (sorry!) +* LOTS of Readme changes + +#### 3.5.2 +* Made `Finalize()` in a way that it can be chained +* Fixed bug in return values from Frame Element contents + +#### 3.6.0 +* Renamed FlexForm to Window +* Removed LookAndFeel capability from Mac platform. + +#### 3.8.0 +* Tab and TabGroup Elements - awesome new capabilities + +#### 1.0.0 Python 2.7 +It's official. There is a 2.7 version of PySimpleGUI! + +#### 3.8.2 +* Exposed `TKOut` in Output Element +* `DrawText` added to Graph Elements +* Removed `Window.UpdateElements` +* `Window.grab_anywere` defaults to False + +#### 3.8.3 +* Listbox, Slider, Combobox, Checkbox, Spin, Tab Group - if change_submits is set, will return the Element's key rather than '' +* Added change_submits capability to Checkbox, Tab Group +* Combobox - Can set value to an Index into the Values table rather than the Value itself +* Warnings added to Drawing routines for Graph element (rather than crashing) +* Window - can "force top level" window to be used rather than a normal window. Means that instead of calling Tk to get a window, will call TopLevel to get the window +* Window Disable / Enable - Disables events (button clicks, etc) for a Window. Use this when you open a second window and want to disable the first window from doing anything. This will simulate a 'dialog box' +* Tab Group returns a value with Window is Read. Return value is the string of the selected tab +* Turned off grab_anywhere for Popups +* New parameter, default_extension, for PopupGetFile +* Keyboard shortcuts for menu items. Can hold ALT key to select items in men +* Removed old-style Tabs - Risky change because it hit fundamental window packing and creation. Will also break any old code using this style tab (sorry folks this is how progress happens) + +#### 3.8.6 + +* Fix for Menus. +* Fixed table colors. Now they work +* Fixed returning keys for tabs +* Window Hide / UnHide methods +* Changed all Popups to remove context manager +* Error checking for Graphing objects and for Element Updates ### 3.9.0 & 1.1.0 * The FIRST UNIFIED version of the code! @@ -3436,10 +3488,10 @@ It's official. There is a 2.7 version of PySimpleGUI! ### 3.9.1 & 1.1.2 * Tab features - * Themes - * Enable / Disable - * Tab text colors - * Selected tab color + * Themes + * Enable / Disable + * Tab text colors + * Selected tab color * New GetListValues method for Listbox * Can now have multiple progress bars in 1 window * Fix for closing debug-output window with other windows open @@ -3473,97 +3525,97 @@ It's official. There is a 2.7 version of PySimpleGUI! * Exposed look and feel table -### Upcoming -Make suggestions people! Future release features - -Port to other graphic engines. Hook up the front-end interface to a backend other than tkinter. Qt, WxPython, etc. WxPython is higher priority. - - - -## Code Condition - - Make it run - Make it right - Make it fast - -It's a recipe for success if done right. PySimpleGUI has completed the "Make it run" phase. It's far from "right" in many ways. These are being worked on. The module is particularly poor for PEP 8 compliance. It was a learning exercise that turned into a somewhat complete GUI solution for lightweight problems. - -While the internals to PySimpleGUI are a tad sketchy, the public interfaces into the SDK are more strictly defined and comply with PEP 8 for the most part. - -Please log bugs and suggestions in the GitHub! It will only make the code stronger and better in the end, a good thing for us all, right? - -## Design - -A moment about the design-spirit of `PySimpleGUI`. From the beginning, this package was meant to take advantage of Python's capabilities with the goal of programming ease. - -**Single File** -While not the best programming practice, the implementation resulted in a single file solution. Only one file is needed, PySimpleGUI.py. You can post this file, email it, and easily import it using one statement. - -**Functions as objects** -In Python, functions behave just like object. When you're placing a Text Element into your form, you may be sometimes calling a function and other times declaring an object. If you use the word Text, then you're getting an object. If you're using `Txt`, then you're calling a function that returns a `Text` object. - -**Lists** -It seemed quite natural to use Python's powerful list constructs when possible. The form is specified as a series of lists. Each "row" of the GUI is represented as a list of Elements. When the form read returns the results to the user, all of the results are presented as a single list. This makes reading a form's values super-simple to do in a single line of Python code. - -**Dictionaries** -Want to view your form's results as a dictionary instead of a list... no problem, just use the `key` keyword on your elements. For complex forms with a lot of values that need to be changed frequently, this is by far the best way of consuming the results. - -You can also look up elements using their keys. This is an excellent way to update elements in reaction to another element. Call `form.FindElement(key)` to get the Element. +### Upcoming +Make suggestions people! Future release features + +Port to other graphic engines. Hook up the front-end interface to a backend other than tkinter. Qt, WxPython, etc. WxPython is higher priority. + + + +## Code Condition + + Make it run + Make it right + Make it fast + +It's a recipe for success if done right. PySimpleGUI has completed the "Make it run" phase. It's far from "right" in many ways. These are being worked on. The module is particularly poor for PEP 8 compliance. It was a learning exercise that turned into a somewhat complete GUI solution for lightweight problems. + +While the internals to PySimpleGUI are a tad sketchy, the public interfaces into the SDK are more strictly defined and comply with PEP 8 for the most part. + +Please log bugs and suggestions in the GitHub! It will only make the code stronger and better in the end, a good thing for us all, right? + +## Design + +A moment about the design-spirit of `PySimpleGUI`. From the beginning, this package was meant to take advantage of Python's capabilities with the goal of programming ease. + +**Single File** +While not the best programming practice, the implementation resulted in a single file solution. Only one file is needed, PySimpleGUI.py. You can post this file, email it, and easily import it using one statement. + +**Functions as objects** +In Python, functions behave just like object. When you're placing a Text Element into your form, you may be sometimes calling a function and other times declaring an object. If you use the word Text, then you're getting an object. If you're using `Txt`, then you're calling a function that returns a `Text` object. + +**Lists** +It seemed quite natural to use Python's powerful list constructs when possible. The form is specified as a series of lists. Each "row" of the GUI is represented as a list of Elements. When the form read returns the results to the user, all of the results are presented as a single list. This makes reading a form's values super-simple to do in a single line of Python code. + +**Dictionaries** +Want to view your form's results as a dictionary instead of a list... no problem, just use the `key` keyword on your elements. For complex forms with a lot of values that need to be changed frequently, this is by far the best way of consuming the results. + +You can also look up elements using their keys. This is an excellent way to update elements in reaction to another element. Call `form.FindElement(key)` to get the Element. **Named / Optional Parameters** This is a language feature that is featured **heavily** in all of the API calls, both functions and classes. Elements are configured, in-place, by setting one or more optional parameters. For example, a Text element's color is chosen by setting the optional `text_color` parameter. - -## Author -MikeTheWatchGuy - -## Demo Code Contributors - - [JorjMcKie](https://github.com/JorjMcKie) - PDF and image viewers (plus a number of code suggestions) -[Otherion](https://github.com/Otherion) - Table Demos Panda & CSV. Loads of suggestions to the core APIs - -## License - -GNU Lesser General Public License (LGPL 3) + - -## Acknowledgments - -* [JorjMcKie](https://github.com/JorjMcKie) was the motivator behind the entire project. His wxsimpleGUI concepts sparked PySimpleGUI into existence -* [Fredrik Lundh](https://wiki.python.org/moin/FredrikLundh) for his work on `tkinter` -* [Ruud van der Ham](https://forum.pythonistacafe.com/u/Ruud) for all the help he's provided as a Python-mentor. Quite a few tricky bits of logic was supplied by Ruud. The dual-purpose return values scheme is Ruud's for example -* **Numerous** users who provided feature suggestions! Many of the cool features were suggested by others. If you were one of them and are willing to take more credit, I'll list you here if you give me permission. Most are too modest -* [moshekaplan](https://github.com/moshekaplan)/**[tkinter_components](https://github.com/moshekaplan/tkinter_components)** wrote the code for the Calendar Chooser Element. It was lifted straight from GitHub -* [Bryan Oakley](https://stackoverflow.com/users/7432/bryan-oakley) for the code that enables the `grab_anywhere` feature. -* [Otherion](https://github.com/Otherion) for help with Tables, being a sounding board for new features, naming functions, ..., all around great help -* [agjunyent](https://github.com/agjunyent) figured out how to properly make tabs and wrote prototype code that demonstrated how to do it -* [jfongattw](https://github.com/jfongattw) huge suggestion... dictionaries. turned out to be -* one of the most critical constructs in PySimpleGUI -* [venim](https://github.com/venim) code to doing Alt-Selections in menus, updating Combobox using index, request to disable windows (a really good idea), checkbox and tab submits on change, returning keys for elements that have change_submits set, ... + +## Author +MikeTheWatchGuy + +## Demo Code Contributors + + [JorjMcKie](https://github.com/JorjMcKie) - PDF and image viewers (plus a number of code suggestions) +[Otherion](https://github.com/Otherion) - Table Demos Panda & CSV. Loads of suggestions to the core APIs + +## License + +GNU Lesser General Public License (LGPL 3) + + +## Acknowledgments + +* [JorjMcKie](https://github.com/JorjMcKie) was the motivator behind the entire project. His wxsimpleGUI concepts sparked PySimpleGUI into existence +* [Fredrik Lundh](https://wiki.python.org/moin/FredrikLundh) for his work on `tkinter` +* [Ruud van der Ham](https://forum.pythonistacafe.com/u/Ruud) for all the help he's provided as a Python-mentor. Quite a few tricky bits of logic was supplied by Ruud. The dual-purpose return values scheme is Ruud's for example +* **Numerous** users who provided feature suggestions! Many of the cool features were suggested by others. If you were one of them and are willing to take more credit, I'll list you here if you give me permission. Most are too modest +* [moshekaplan](https://github.com/moshekaplan)/**[tkinter_components](https://github.com/moshekaplan/tkinter_components)** wrote the code for the Calendar Chooser Element. It was lifted straight from GitHub +* [Bryan Oakley](https://stackoverflow.com/users/7432/bryan-oakley) for the code that enables the `grab_anywhere` feature. +* [Otherion](https://github.com/Otherion) for help with Tables, being a sounding board for new features, naming functions, ..., all around great help +* [agjunyent](https://github.com/agjunyent) figured out how to properly make tabs and wrote prototype code that demonstrated how to do it +* [jfongattw](https://github.com/jfongattw) huge suggestion... dictionaries. turned out to be +* one of the most critical constructs in PySimpleGUI +* [venim](https://github.com/venim) code to doing Alt-Selections in menus, updating Combobox using index, request to disable windows (a really good idea), checkbox and tab submits on change, returning keys for elements that have change_submits set, ... * [rtrrtr](https://github.com/rtrrtr) Helped get the 2.7 and 3.x code unified (big damned deal) * Tony Crewe (anthony.crewe@gmail.com) Generously provided his classroom materials that he has written to teach a GUI course. If you're an educator and want to trade materials with Tony, he would like to hear from you. - -## How Do I -Finally, I must thank the fine folks at How Do I. -https://github.com/gleitz/howdoi -Their utility has forever changed the way and pace in which I can program. I urge you to try the HowDoI.py application here on GitHub. Trust me, **it's going to be worth the effort!** -Here are the steps to run that application - - Install howdoi: - pip install howdoi - Test your install: - python -m howdoi howdoi.py - To run it: - Python HowDoI.py - -The pip command is all there is to the setup. - -The way HowDoI works is that it uses your search term to look through stack overflow posts. It finds the best answer, gets the code from the answer, and presents it as a response. It gives you the correct answer OFTEN. It's a miracle that it work SO well. -For Python questions, I simply start my query with 'Python'. Let's say you forgot how to reverse a list in Python. When you run HowDoI and ask this question, this is what you'll see. - -![howdoiwithhistory](https://user-images.githubusercontent.com/13696193/45064009-5fd61180-b07f-11e8-8ead-eb0d1ff3a6be.jpg) - - - -In the hands of a competent programmer, this tool is **amazing**. It's a must-try kind of program that has completely changed my programming process. I'm not afraid of asking for help! You just have to be smart about using what you find. - -The PySimpleGUI window that the results are shown in is an 'input' field which means you can copy and paste the results right into your code. + +## How Do I +Finally, I must thank the fine folks at How Do I. +https://github.com/gleitz/howdoi +Their utility has forever changed the way and pace in which I can program. I urge you to try the HowDoI.py application here on GitHub. Trust me, **it's going to be worth the effort!** +Here are the steps to run that application + + Install howdoi: + pip install howdoi + Test your install: + python -m howdoi howdoi.py + To run it: + Python HowDoI.py + +The pip command is all there is to the setup. + +The way HowDoI works is that it uses your search term to look through stack overflow posts. It finds the best answer, gets the code from the answer, and presents it as a response. It gives you the correct answer OFTEN. It's a miracle that it work SO well. +For Python questions, I simply start my query with 'Python'. Let's say you forgot how to reverse a list in Python. When you run HowDoI and ask this question, this is what you'll see. + +![howdoiwithhistory](https://user-images.githubusercontent.com/13696193/45064009-5fd61180-b07f-11e8-8ead-eb0d1ff3a6be.jpg) + + + +In the hands of a competent programmer, this tool is **amazing**. It's a must-try kind of program that has completely changed my programming process. I'm not afraid of asking for help! You just have to be smart about using what you find. + +The PySimpleGUI window that the results are shown in is an 'input' field which means you can copy and paste the results right into your code. \ No newline at end of file diff --git a/readme.md b/readme.md index b75f4328..9973699c 100644 --- a/readme.md +++ b/readme.md @@ -1,300 +1,302 @@ - - - - -![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) - -[![Downloads](http://pepy.tech/badge/pysimplegui)](http://pepy.tech/project/pysimplegui) -[![Downloads ](https://pepy.tech/badge/pysimplegui27)](https://pepy.tech/project/pysimplegui27) -![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) - ![Awesome Meter](https://img.shields.io/badge/Awesome_meter-100-yellow.svg) - ![Python Version](https://img.shields.io/badge/Python-2.7_3.x-yellow.svg) - - - - - - -# PySimpleGUI - - -## Now supports both Python 2.7 & 3 - -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.9.3-red.svg?longCache=true&style=for-the-badge) - - ![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.1.3-blue.svg?longCache=true&style=for-the-badge) - -[Announcements of Latest Developments](https://github.com/MikeTheWatchGuy/PySimpleGUI/issues/142) - -[ReadTheDocs](http://pysimplegui.readthedocs.io/) - -[COOKBOOK!](https://pysimplegui.readthedocs.io/en/latest/cookbook/) - -[Brief Tutorial](https://pysimplegui.readthedocs.io/en/latest/tutorial/) - -[Latest Demos and Master Branch on GitHub](https://github.com/MikeTheWatchGuy/PySimpleGUI) - -[Docs in PDF Format](https://github.com/MikeTheWatchGuy/PySimpleGUI/tree/master/docs) - - + + + + + + +![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) + +[![Downloads](http://pepy.tech/badge/pysimplegui)](http://pepy.tech/project/pysimplegui) +[![Downloads ](https://pepy.tech/badge/pysimplegui27)](https://pepy.tech/project/pysimplegui27) +![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) + ![Awesome Meter](https://img.shields.io/badge/Awesome_meter-100-yellow.svg) + ![Python Version](https://img.shields.io/badge/Python-2.7_3.x-yellow.svg) + + + + + + +# PySimpleGUI + + +## Now supports both Python 2.7 & 3 + +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.9.3-red.svg?longCache=true&style=for-the-badge) + + ![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.1.3-blue.svg?longCache=true&style=for-the-badge) + +[Announcements of Latest Developments](https://github.com/MikeTheWatchGuy/PySimpleGUI/issues/142) + +[ReadTheDocs](http://pysimplegui.readthedocs.io/) + +[COOKBOOK!](https://pysimplegui.readthedocs.io/en/latest/cookbook/) + +[Brief Tutorial](https://pysimplegui.readthedocs.io/en/latest/tutorial/) + +[Latest Demos and Master Branch on GitHub](https://github.com/MikeTheWatchGuy/PySimpleGUI) + +[Docs in PDF Format](https://github.com/MikeTheWatchGuy/PySimpleGUI/tree/master/docs) + + Super-simple GUI to use... Powerfully customizable - -Home of the 1-line custom GUI & 1-line progress meter + +Home of the 1-line custom GUI & 1-line progress meter The native GUI framework for perfectionists with deadlines - -#### Note regarding Python versions -As of 9/25/2018 **both Python 3 and Python 2.7 are supported**! The Python 3 version is named `PySimpleGUI`. The Python 2.7 version is `PySimpleGUI27`. They are installed separately and the imports are different. See instructions in Installation section for more info. - - ------------------------------------------------------------------------- - - -Looking for a GUI package? -* Taking your Python code from the world of command lines and into the convenience of a GUI? * -* Have a Raspberry **Pi** with a touchscreen that's going to waste because you don't have the time to learn a GUI SDK? -* Into Machine Learning and are sick of the command line? -* Would like to distribute your Python code to Windows users as a single .EXE file that launches straight into a GUI, much like a WinForms app? - -Look no further, **you've found your GUI package**. - - import PySimpleGUI as sg - - sg.Popup('Hello From PySimpleGUI!', 'This is the shortest GUI program ever!') - - -![hello world](https://user-images.githubusercontent.com/13696193/44960047-1f7f6380-aec6-11e8-9d5e-12ef935bcade.jpg) - - -Or how about a ***custom GUI*** in 1 line of code? - - import PySimpleGUI as sg - - button, (filename,) = sg.Window('Get filename example'). Layout([[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ]).Read() - -![get filename](https://user-images.githubusercontent.com/13696193/44960039-f1018880-aec5-11e8-8a43-3d7f8ff93b67.jpg) - - - Build beautiful customized windows that fit your specific problem. Let PySimpleGUI solve your GUI problem while you solve your real problems. Look through the Cookbook, find a matching recipe, copy, paste, run within minutes. This is the process PySimpleGUI was designed to facilitate. - - -![borderless grayed buttons](https://user-images.githubusercontent.com/13696193/45168664-d848e980-b1c9-11e8-886e-63279ae4017f.jpg) - - - -PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkinter, but you interact with them in a more friendly way. It does the layout and boilerplate code for you and presents you with a simple, efficient interface. - -![everything dark theme](https://user-images.githubusercontent.com/13696193/44959854-b1d23800-aec3-11e8-90b6-5af915a86d15.jpg) - -Perhaps you're looking for a way to interact with your **Raspberry Pi** in a more friendly way. The same for shown as on Pi (roughly the same) - -![raspberry pi everything demo](https://user-images.githubusercontent.com/13696193/44279694-5b58ce80-a220-11e8-9ab6-d6021f5a944f.jpg) - - - -In addition to a primary GUI, you can add a Progress Meter to your code with ONE LINE of code. Slide this line into any of your `for` loops and get a nice meter: - - OneLineProgressMeter('My meter title', current_value, max value, 'key') - - ![easyprogressmeter](https://user-images.githubusercontent.com/13696193/44960065-83099100-aec6-11e8-8aa8-96e4b100a0e4.jpg) - -You can build an async media player GUI with custom buttons in 30 lines of code. - -![media player 2](https://user-images.githubusercontent.com/13696193/44960091-eeebf980-aec6-11e8-884e-80d4447a83cd.jpg) - - -How about embedding a game inside of a GUI? This game of Pong is written in tkinter and then dropped into the PySimpleGUI window creating a game that has an accompanying GUI. - -![pong](https://user-images.githubusercontent.com/13696193/45860012-2d8d0b00-bd33-11e8-9efd-3eaf4c30f324.gif) - - -Combining PySimpleGUI with PyInstaller creates something truly remarkable and special, a Python program that looks like a Windows WinForms application. This application with working menu was created in 20 lines of Python code. It is a single .EXE file that launches straight into the screen you see. And more good news, the only icon you see on the taskbar is the window itself... there is no pesky shell window. - -![menu demo](https://user-images.githubusercontent.com/13696193/45923097-8fbc4c00-beaa-11e8-87d2-01a5331811c8.gif) - - - ## Background -I was frustrated by having to deal with the dos prompt when I had a powerful Windows machine right in front of me. Why is it SO difficult to do even the simplest of input/output to a window in Python?? - -There are a number of 'easy to use' Python GUIs, but they were too limited for my requirements. PySimpleGUI aims for the same simplicity found in packages like `EasyGUI`and `WxSimpleGUI` , both really handy but limited, and adds the ability to define your own layouts. This ability to make your own windows using a large palette of widgets is but one difference between the existing "simple" packages and `PySimpleGUI`. - -With a simple GUI, it becomes practical to "associate" .py files with the python interpreter on Windows. Double click a py file and up pops a GUI window, a more pleasant experience than opening a dos Window and typing a command line. - -The `PySimpleGUI` package is focused on the ***developer***. -> Create a custom GUI with as little and as simple code as possible. - -This was the primary focus used to create PySimpleGUI. - -> "Do it in a Python-like way" - -was the second. - -## Features - -While simple to use, PySimpleGUI has significant depth to be explored by more advanced programmers. The feature set goes way beyond the requirements of a beginner programmer, and into the required features needed for complex GUIs. - - Features of PySimpleGUI include: - Support for Python versions 2.7 and 3 - Text - Single Line Input - Buttons including these types: - File Browse - Files Browse - Folder Browse - SaveAs - Non-closing return - Close window - Realtime - Calendar chooser - Color chooser - Checkboxes - Radio Buttons - Listbox - Option Menu - Slider - Graph - Frame with title - Icons - Multi-line Text Input - Scroll-able Output - Images + +#### Note regarding Python versions +As of 9/25/2018 **both Python 3 and Python 2.7 are supported**! The Python 3 version is named `PySimpleGUI`. The Python 2.7 version is `PySimpleGUI27`. They are installed separately and the imports are different. See instructions in Installation section for more info. + + +------------------------------------------------------------------------ + + +Looking for a GUI package? +* Taking your Python code from the world of command lines and into the convenience of a GUI? * +* Have a Raspberry **Pi** with a touchscreen that's going to waste because you don't have the time to learn a GUI SDK? +* Into Machine Learning and are sick of the command line? +* Would like to distribute your Python code to Windows users as a single .EXE file that launches straight into a GUI, much like a WinForms app? + +Look no further, **you've found your GUI package**. + + import PySimpleGUI as sg + + sg.Popup('Hello From PySimpleGUI!', 'This is the shortest GUI program ever!') + + +![hello world](https://user-images.githubusercontent.com/13696193/44960047-1f7f6380-aec6-11e8-9d5e-12ef935bcade.jpg) + + +Or how about a ***custom GUI*** in 1 line of code? + + import PySimpleGUI as sg + + event, (filename,) = sg.Window('Get filename example'). Layout([[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ]).Read() + +![get filename](https://user-images.githubusercontent.com/13696193/44960039-f1018880-aec5-11e8-8a43-3d7f8ff93b67.jpg) + + + Build beautiful customized windows that fit your specific problem. Let PySimpleGUI solve your GUI problem while you solve your real problems. Look through the Cookbook, find a matching recipe, copy, paste, run within minutes. This is the process PySimpleGUI was designed to facilitate. + + +![borderless grayed buttons](https://user-images.githubusercontent.com/13696193/45168664-d848e980-b1c9-11e8-886e-63279ae4017f.jpg) + + + +PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkinter, but you interact with them in a more friendly way. It does the layout and boilerplate code for you and presents you with a simple, efficient interface. + +![everything dark theme](https://user-images.githubusercontent.com/13696193/44959854-b1d23800-aec3-11e8-90b6-5af915a86d15.jpg) + +Perhaps you're looking for a way to interact with your **Raspberry Pi** in a more friendly way. The same for shown as on Pi (roughly the same) + +![raspberry pi everything demo](https://user-images.githubusercontent.com/13696193/44279694-5b58ce80-a220-11e8-9ab6-d6021f5a944f.jpg) + + + +In addition to a primary GUI, you can add a Progress Meter to your code with ONE LINE of code. Slide this line into any of your `for` loops and get a nice meter: + + OneLineProgressMeter('My meter title', current_value, max value, 'key') + + ![easyprogressmeter](https://user-images.githubusercontent.com/13696193/44960065-83099100-aec6-11e8-8aa8-96e4b100a0e4.jpg) + +You can build an async media player GUI with custom buttons in 30 lines of code. + +![media player 2](https://user-images.githubusercontent.com/13696193/44960091-eeebf980-aec6-11e8-884e-80d4447a83cd.jpg) + + +How about embedding a game inside of a GUI? This game of Pong is written in tkinter and then dropped into the PySimpleGUI window creating a game that has an accompanying GUI. + +![pong](https://user-images.githubusercontent.com/13696193/45860012-2d8d0b00-bd33-11e8-9efd-3eaf4c30f324.gif) + + +Combining PySimpleGUI with PyInstaller creates something truly remarkable and special, a Python program that looks like a Windows WinForms application. This application with working menu was created in 20 lines of Python code. It is a single .EXE file that launches straight into the screen you see. And more good news, the only icon you see on the taskbar is the window itself... there is no pesky shell window. + +![menu demo](https://user-images.githubusercontent.com/13696193/45923097-8fbc4c00-beaa-11e8-87d2-01a5331811c8.gif) + + + ## Background +I was frustrated by having to deal with the dos prompt when I had a powerful Windows machine right in front of me. Why is it SO difficult to do even the simplest of input/output to a window in Python?? + +There are a number of 'easy to use' Python GUIs, but they were too limited for my requirements. PySimpleGUI aims for the same simplicity found in packages like `EasyGUI`and `WxSimpleGUI` , both really handy but limited, and adds the ability to define your own layouts. This ability to make your own windows using a large palette of widgets is but one difference between the existing "simple" packages and `PySimpleGUI`. + +With a simple GUI, it becomes practical to "associate" .py files with the python interpreter on Windows. Double click a py file and up pops a GUI window, a more pleasant experience than opening a dos Window and typing a command line. + +The `PySimpleGUI` package is focused on the ***developer***. +> Create a custom GUI with as little and as simple code as possible. + +This was the primary focus used to create PySimpleGUI. + +> "Do it in a Python-like way" + +was the second. + +## Features + +While simple to use, PySimpleGUI has significant depth to be explored by more advanced programmers. The feature set goes way beyond the requirements of a beginner programmer, and into the required features needed for complex GUIs. + + Features of PySimpleGUI include: + Support for Python versions 2.7 and 3 + Text + Single Line Input + Buttons including these types: + File Browse + Files Browse + Folder Browse + SaveAs + Non-closing return + Close window + Realtime + Calendar chooser + Color chooser + Checkboxes + Radio Buttons + Listbox + Option Menu + Slider + Graph + Frame with title + Icons + Multi-line Text Input + Scroll-able Output + Images Tables Trees - Progress Bar Async/Non-Blocking Windows - Tabbed windows - Persistent Windows - Redirect Python Output/Errors to scrolling window - 'Higher level' APIs (e.g. MessageBox, YesNobox, ...) - Single-Line-Of-Code Proress Bar & Debug Print - Complete control of colors, look and feel - Selection of pre-defined palettes - Button images - Return values as dictionary - Set focus - Bind return key to buttons - Group widgets into a column and place into window anywhere - Scrollable columns - Keyboard low-level key capture - Mouse scroll-wheel support - Get Listbox values as they are selected - Get slider, spinner, combo as they are changed - Update elements in a live window - Bulk window-fill operation - Save / Load window to/from disk - Borderless (no titlebar) windows - Always on top windows - Menus with ALT-hotkey - Tooltips - Clickable links - No async programming required (no callbacks to worry about) - - -An example of many widgets used on a single window. A little further down you'll find the 21 lines of code required to create this complex window. Try it if you don't believe it. Install PySimpleGUI then : - ->Start Python, copy and paste the code below into the >>> prompt and hit enter. This will pop up... -> - -![everything example](https://user-images.githubusercontent.com/13696193/43097412-0a4652aa-8e8a-11e8-8e09-939484e3c568.jpg) - - - - import PySimpleGUI as sg - - layout = [[sg.Text('All graphic widgets in one window!', size=(30, 1), font=("Helvetica", 25), text_color='blue')], - [sg.Text('Here is some text.... and a place to enter text')], - [sg.InputText()], - [sg.Checkbox('My first checkbox!'), sg.Checkbox('My second checkbox!', default=True)], - [sg.Radio('My first Radio! ', "RADIO1", default=True), sg.Radio('My second Radio!', "RADIO1")], - [sg.Multiline(default_text='This is the default Text shoulsd you decide not to type anything',)], - [sg.InputCombo(['Combobox 1', 'Combobox 2'], size=(20, 3)), - sg.Slider(range=(1, 100), orientation='h', size=(35, 20), default_value=85)], - [sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6)), - sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=25), - sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=75), - sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=10)], - [sg.Text('_' * 100, size=(70, 1))], - [sg.Text('Choose Source and Destination Folders', size=(35, 1))], - [sg.Text('Source Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Source'), - sg.FolderBrowse()], - [sg.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Dest'), - sg.FolderBrowse()], - [sg.Submit(), sg.Cancel(), sg.Button('Customized', button_color=('white', 'green'))]] - - button, values = sg.Window('Everything bagel', auto_size_text=True, default_element_size=(40, 1)).Layout(layout).Read() - - - ---- -### Design Goals - -> Copy, Paste, Run. - -`PySimpleGUI's` goal with the API is to be easy on the programmer, and to function in a Python-like way. Since GUIs are visual, it was desirable for the code to visually match what's on the screen. By providing a significant amount of documentation and an easy to use Cookbook, it's possible to see your first GUI within 5 minutes of beginning the installation. - - > Be Pythonic - - Be Pythonic... Attempted to use language constructs in a natural way and to exploit some of Python's interesting features. Python's lists and optional parameters make PySimpleGUI work smoothly. - - - windows are represented as Python lists. - - A window is a list of rows - - A row is a list of elements -- Return values are a list of button presses and input values. -- Return values can also be represented as a dictionary -- The SDK calls collapse down into a single line of Python code that presents a custom GUI and returns values -- Linear programming instead of callbacks - - #### Lofty Goals - -> Change Python - -The hope is not that ***this*** package will become part of the Python Standard Library. - -The hope is that Python will become ***the*** go-to language for creating GUI programs that run on Windows, Mac, and Linux *for all levels of developer*. - -The hope is that beginners that are interested in graphic design will have an easy way to express themselves, right from the start of their Python experience. - -There is a noticeable gap in the Python GUI solution. Fill that gap and who knows what will happen. - -Maybe there's no "there there". ***Or*** maybe a simple GUI API will enable Python to dominate yet another computing discipline like it has so many others. This is my attempt to find out. - - - ----- -## Getting Started with PySimpleGUI - -### Installing Python 3 - - pip install --upgrade PySimpleGUI - -On some systems you need to run pip3. - - pip3 install --upgrade PySimpleGUI - -On a Raspberry Pi, this is should work: - - sudo pip3 install --upgrade pysimplegui - -Some users have found that upgrading required using an extra flag on the pip `--no-cache-dir`. - - pip install --upgrade --no-cache-dir - -On some versions of Linux you will need to first install pip. Need the Chicken before you can get the Egg (get it... Egg?) - -`sudo apt install python3-pip ` - -If for some reason you are unable to install using `pip`, don't worry, you can still import PySimpleGUI by downloading the file PySimleGUI.py and placing it in your folder along with the application that is importing it. - -`tkinter` is a requirement for PySimpleGUI (the only requirement). Some OS variants, such as Ubuntu, do not some with `tkinter` already installed. If you get an error similar to: -``` -ImportError: No module named tkinter -``` -then yosudou need to install `tkinter`. Be sure and get the Python 3 version. -` -```sudo apt-get install python3-tk ``` - - -### Installing for Python 2.7 - + Progress Bar Async/Non-Blocking Windows + Tabbed windows + Persistent Windows + Redirect Python Output/Errors to scrolling window + 'Higher level' APIs (e.g. MessageBox, YesNobox, ...) + Single-Line-Of-Code Proress Bar & Debug Print + Complete control of colors, look and feel + Selection of pre-defined palettes + Button images + Return values as dictionary + Set focus + Bind return key to buttons + Group widgets into a column and place into window anywhere + Scrollable columns + Keyboard low-level key capture + Mouse scroll-wheel support + Get Listbox values as they are selected + Get slider, spinner, combo as they are changed + Update elements in a live window + Bulk window-fill operation + Save / Load window to/from disk + Borderless (no titlebar) windows + Always on top windows + Menus with ALT-hotkey + Tooltips + Clickable links + No async programming required (no callbacks to worry about) + + +An example of many widgets used on a single window. A little further down you'll find the 21 lines of code required to create this complex window. Try it if you don't believe it. Install PySimpleGUI then : + +>Start Python, copy and paste the code below into the >>> prompt and hit enter. This will pop up... +> + +![everything example](https://user-images.githubusercontent.com/13696193/43097412-0a4652aa-8e8a-11e8-8e09-939484e3c568.jpg) + + + + import PySimpleGUI as sg + + layout = [[sg.Text('All graphic widgets in one window!', size=(30, 1), font=("Helvetica", 25), text_color='blue')], + [sg.Text('Here is some text.... and a place to enter text')], + [sg.InputText()], + [sg.Checkbox('My first checkbox!'), sg.Checkbox('My second checkbox!', default=True)], + [sg.Radio('My first Radio! ', "RADIO1", default=True), sg.Radio('My second Radio!', "RADIO1")], + [sg.Multiline(default_text='This is the default Text shoulsd you decide not to type anything',)], + [sg.InputCombo(['Combobox 1', 'Combobox 2'], size=(20, 3)), + sg.Slider(range=(1, 100), orientation='h', size=(35, 20), default_value=85)], + [sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6)), + sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=25), + sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=75), + sg.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=10)], + [sg.Text('_' * 100, size=(70, 1))], + [sg.Text('Choose Source and Destination Folders', size=(35, 1))], + [sg.Text('Source Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Source'), + sg.FolderBrowse()], + [sg.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), sg.InputText('Dest'), + sg.FolderBrowse()], + [sg.Submit(), sg.Cancel(), sg.Button('Customized', button_color=('white', 'green'))]] + + event, values = sg.Window('Everything bagel', auto_size_text=True, default_element_size=(40, 1)).Layout(layout).Read() + + + +--- +### Design Goals + +> Copy, Paste, Run. + +`PySimpleGUI's` goal with the API is to be easy on the programmer, and to function in a Python-like way. Since GUIs are visual, it was desirable for the code to visually match what's on the screen. By providing a significant amount of documentation and an easy to use Cookbook, it's possible to see your first GUI within 5 minutes of beginning the installation. + + > Be Pythonic + + Be Pythonic... Attempted to use language constructs in a natural way and to exploit some of Python's interesting features. Python's lists and optional parameters make PySimpleGUI work smoothly. + + - windows are represented as Python lists. + - A window is a list of rows + - A row is a list of elements +- Return values are a list of button presses and input values. +- Return values can also be represented as a dictionary +- The SDK calls collapse down into a single line of Python code that presents a custom GUI and returns values +- Linear programming instead of callbacks + + #### Lofty Goals + +> Change Python + +The hope is not that ***this*** package will become part of the Python Standard Library. + +The hope is that Python will become ***the*** go-to language for creating GUI programs that run on Windows, Mac, and Linux *for all levels of developer*. + +The hope is that beginners that are interested in graphic design will have an easy way to express themselves, right from the start of their Python experience. + +There is a noticeable gap in the Python GUI solution. Fill that gap and who knows what will happen. + +Maybe there's no "there there". ***Or*** maybe a simple GUI API will enable Python to dominate yet another computing discipline like it has so many others. This is my attempt to find out. + + + ----- +## Getting Started with PySimpleGUI + +### Installing Python 3 + + pip install --upgrade PySimpleGUI + +On some systems you need to run pip3. + + pip3 install --upgrade PySimpleGUI + +On a Raspberry Pi, this is should work: + + sudo pip3 install --upgrade pysimplegui + +Some users have found that upgrading required using an extra flag on the pip `--no-cache-dir`. + + pip install --upgrade --no-cache-dir + +On some versions of Linux you will need to first install pip. Need the Chicken before you can get the Egg (get it... Egg?) + +`sudo apt install python3-pip ` + +If for some reason you are unable to install using `pip`, don't worry, you can still import PySimpleGUI by downloading the file PySimleGUI.py and placing it in your folder along with the application that is importing it. + +`tkinter` is a requirement for PySimpleGUI (the only requirement). Some OS variants, such as Ubuntu, do not some with `tkinter` already installed. If you get an error similar to: +``` +ImportError: No module named tkinter +``` +then yosudou need to install `tkinter`. Be sure and get the Python 3 version. +` +```sudo apt-get install python3-tk ``` + + +### Installing for Python 2.7 + ` pip install --upgrade PySimpleGUI27` or ` pip2 install --upgrade PySimpleGUI27` @@ -302,173 +304,173 @@ then yosudou need to install `tkinter`. Be sure and get the Python 3 version. You may need to also install "future" for version 2.7 pip install future - or + or ` pip2 install future` -Python 2.7 support is relatively new and the bugs are still being worked out. I'm unsure what may need to be done to install tkinter for Python 2.7. Will update this readme when more info is available - -Like above, you may have to install either pip or tkinter. To do this on Python 2.7: - -`sudo apt install python-pip` - -`sudo apt install python-tkinter` +Python 2.7 support is relatively new and the bugs are still being worked out. I'm unsure what may need to be done to install tkinter for Python 2.7. Will update this readme when more info is available + +Like above, you may have to install either pip or tkinter. To do this on Python 2.7: + +`sudo apt install python-pip` + +`sudo apt install python-tkinter` + + + +### Testing your installation + +Once you have installed, or copied the .py file to your app folder, you can test the installation using python. At the command prompt start up Python. + +#### Instructions for Python 2.7: +``` +python +>>> import PySimpleGUI27 +>>> PySimpleGUI27.main() +``` + +#### Instructions for Python 3: + +``` +python3 +>>> import PySimpleGUI +>>> PySimpleGUI.main() +``` + +You will see a sample window in the center of your screen. If it's not installed correctly you are likely to get an error message during one of those commands + +Here is the window you should see: + +![sample window](https://user-images.githubusercontent.com/13696193/46097669-79efa500-c190-11e8-885c-e5d4d5d09ea6.jpg) + + + +### Prerequisites +Python 2.7 or Python 3 +tkinter + +PySimpleGUI Runs on all Python3 platforms that have tkinter running on them. It has been tested on Windows, Mac, Linux, Raspberry Pi. Even runs on `pypy3`. + +### EXE file creation + +If you wish to create an EXE from your PySimpleGUI application, you will need to install `PyInstaller`. There are instructions on how to create an EXE at the bottom of this ReadMe + + +## Using - Python 3 + +To use in your code, simply import.... + `import PySimpleGUI as sg` + +Then use either "high level" API calls or build your own windows. + + sg.Popup('This is my first Popup') + +![first popup](https://user-images.githubusercontent.com/13696193/44957300-c7813680-ae9e-11e8-9a8c-c70198db7907.jpg) + + +Yes, it's just that easy to have a window appear on the screen using Python. With PySimpleGUI, making a custom window appear isn't much more difficult. The goal is to get you running on your GUI within ***minutes***, not hours nor days. + +## Using - Python 2.7 + +Those using Python 2.7 will import a different module name + `import PySimpleGUI27 as sg` + +## Code Samples Assume Python 3 + +While all of the code examples you will see in this Readme and the Cookbook assume Python 3 and thus have an `import PySimpleGUI` at the top, you can run ***all*** of this code on Python 2.7 by changing the import statement to `import PySimpleGUI27` + +--- +## APIs + +PySimpleGUI can be broken down into 2 types of API's: + * High Level single call functions (The `Popup` calls) + * Custom window functions + + +### Python Language Features + + There are a number of Python language features that PySimpleGUI utilizes heavily for API access that should be understood... + * Variable number of arguments to a function call + * Optional parameters to a function call + * Dictionaries + +#### Variable Number of Arguments + + The "High Level" API calls that *output* values take a variable number of arguments so that they match a "print" statement as much as possible. The idea is to make it simple for the programmer to output as many items as desired and in any format. The user need not convert the variables to be output into the strings. The PySimpleGUI functions do that for the user. + + sg.Popup('Variable number of parameters example', var1, var2, "etc") + +Each new item begins on a new line in the Popup + + ![snap0179](https://user-images.githubusercontent.com/13696193/43658129-f6ca49c6-9725-11e8-9317-1f77443eb04a.jpg) + + + +#### Optional Parameters to a Function Call + +This feature of the Python language is utilized ***heavily*** as a method of customizing windows and window Elements. Rather than requiring the programmer to specify every possible option for a widget, instead only the options the caller wants to override are specified. + +Here is the function definition for the Popup function. The details aren't important. What is important is seeing that there is a long list of potential tweaks that a caller can make. However, they don't *have* to be specified on each and every call. + + def Popup(*args, + button_color=None, + button_type=MSG_BOX_OK, + auto_close=False, + auto_close_duration=None, + icon=DEFAULT_WINDOW_ICON, + line_width=MESSAGE_BOX_LINE_WIDTH, + font=None): + +If the caller wanted to change the button color to be black on yellow, the call would look something like this: + + sg.Popup('This box has a custom button color', button_color=('black', 'yellow')) + + +![snap0180](https://user-images.githubusercontent.com/13696193/43658171-13a72bfe-9726-11e8-8c7a-0a46e46fb202.jpg) + + +#### Dictionaries + +Dictionaries are used by more advanced PySimpleGUI users. You'll know that dictionaries are being used if you see a `key` parameter on any Element. Dictionaries are used in 2 ways: +1. To identify values when a window is read +2. To identify Elements so that they can be "updated" + +--- + +## High Level API Calls - Popup's + +"High level calls" are those that start with "Popup". They are the most basic form of communications with the user. They are named after the type of window they create, a pop-up window. These windows are meant to be short lived while, either delivering information or collecting it, and then quickly disappearing. + +## Popup Output + +Think of the `Popup` call as the GUI equivalent of a `print` statement. It's your way of displaying results to a user in the windowed world. Each call to Popup will create a new Popup window. + +`Popup` calls are normally blocking. your program will stop executing until the user has closed the Popup window. A non-blocking window of Popup discussed in the async section. + +Just like a print statement, you can pass any number of arguments you wish. They will all be turned into strings and displayed in the popup window. + +There are a number of Popup output calls, each with a slightly different look (e.g. different button labels). + +The list of Popup output functions are + + Popup + PopupOk + PopupYesNo + PopupCancel + PopupOkCancel + PopupError + PopupTimed, PopupAutoClose + PopupNoWait, PopupNonBlocking + +The trailing portion of the function name after Popup indicates what buttons are shown. `PopupYesNo` shows a pair of button with Yes and No on them. `PopupCancel` has a Cancel button, etc. + +While these are "output" windows, they do collect input in the form of buttons. The Popup functions return the button that was clicked. If the Ok button was clicked, then Popup returns the string 'Ok'. If the user clicked the X button to close the window, then the button value returned is `None`. + +The function `PopupTimed` or `PopupAutoClose` are popup windows that will automatically close after come period of time. + +Here is a quick-reference showing how the Popup calls look. - -### Testing your installation - -Once you have installed, or copied the .py file to your app folder, you can test the installation using python. At the command prompt start up Python. - -#### Instructions for Python 2.7: -``` -python ->>> import PySimpleGUI27 ->>> PySimpleGUI27.main() -``` - -#### Instructions for Python 3: - -``` -python3 ->>> import PySimpleGUI ->>> PySimpleGUI.main() -``` - -You will see a sample window in the center of your screen. If it's not installed correctly you are likely to get an error message during one of those commands - -Here is the window you should see: - -![sample window](https://user-images.githubusercontent.com/13696193/46097669-79efa500-c190-11e8-885c-e5d4d5d09ea6.jpg) - - - -### Prerequisites -Python 2.7 or Python 3 -tkinter - -PySimpleGUI Runs on all Python3 platforms that have tkinter running on them. It has been tested on Windows, Mac, Linux, Raspberry Pi. Even runs on `pypy3`. - -### EXE file creation - -If you wish to create an EXE from your PySimpleGUI application, you will need to install `PyInstaller`. There are instructions on how to create an EXE at the bottom of this ReadMe - - -## Using - Python 3 - -To use in your code, simply import.... - `import PySimpleGUI as sg` - -Then use either "high level" API calls or build your own windows. - - sg.Popup('This is my first Popup') - -![first popup](https://user-images.githubusercontent.com/13696193/44957300-c7813680-ae9e-11e8-9a8c-c70198db7907.jpg) - - -Yes, it's just that easy to have a window appear on the screen using Python. With PySimpleGUI, making a custom window appear isn't much more difficult. The goal is to get you running on your GUI within ***minutes***, not hours nor days. - -## Using - Python 2.7 - -Those using Python 2.7 will import a different module name - `import PySimpleGUI27 as sg` - -## Code Samples Assume Python 3 - -While all of the code examples you will see in this Readme and the Cookbook assume Python 3 and thus have an `import PySimpleGUI` at the top, you can run ***all*** of this code on Python 2.7 by changing the import statement to `import PySimpleGUI27` - ---- -## APIs - -PySimpleGUI can be broken down into 2 types of API's: - * High Level single call functions (The `Popup` calls) - * Custom window functions - - -### Python Language Features - - There are a number of Python language features that PySimpleGUI utilizes heavily for API access that should be understood... - * Variable number of arguments to a function call - * Optional parameters to a function call - * Dictionaries - -#### Variable Number of Arguments - - The "High Level" API calls that *output* values take a variable number of arguments so that they match a "print" statement as much as possible. The idea is to make it simple for the programmer to output as many items as desired and in any format. The user need not convert the variables to be output into the strings. The PySimpleGUI functions do that for the user. - - sg.Popup('Variable number of parameters example', var1, var2, "etc") - -Each new item begins on a new line in the Popup - - ![snap0179](https://user-images.githubusercontent.com/13696193/43658129-f6ca49c6-9725-11e8-9317-1f77443eb04a.jpg) - - - -#### Optional Parameters to a Function Call - -This feature of the Python language is utilized ***heavily*** as a method of customizing windows and window Elements. Rather than requiring the programmer to specify every possible option for a widget, instead only the options the caller wants to override are specified. - -Here is the function definition for the Popup function. The details aren't important. What is important is seeing that there is a long list of potential tweaks that a caller can make. However, they don't *have* to be specified on each and every call. - - def Popup(*args, - button_color=None, - button_type=MSG_BOX_OK, - auto_close=False, - auto_close_duration=None, - icon=DEFAULT_WINDOW_ICON, - line_width=MESSAGE_BOX_LINE_WIDTH, - font=None): - -If the caller wanted to change the button color to be black on yellow, the call would look something like this: - - sg.Popup('This box has a custom button color', button_color=('black', 'yellow')) - - -![snap0180](https://user-images.githubusercontent.com/13696193/43658171-13a72bfe-9726-11e8-8c7a-0a46e46fb202.jpg) - - -#### Dictionaries - -Dictionaries are used by more advanced PySimpleGUI users. You'll know that dictionaries are being used if you see a `key` parameter on any Element. Dictionaries are used in 2 ways: -1. To identify values when a window is read -2. To identify Elements so that they can be "updated" - ---- - -## High Level API Calls - Popup's - -"High level calls" are those that start with "Popup". They are the most basic form of communications with the user. They are named after the type of window they create, a pop-up window. These windows are meant to be short lived while, either delivering information or collecting it, and then quickly disappearing. - -## Popup Output - -Think of the `Popup` call as the GUI equivalent of a `print` statement. It's your way of displaying results to a user in the windowed world. Each call to Popup will create a new Popup window. - -`Popup` calls are normally blocking. your program will stop executing until the user has closed the Popup window. A non-blocking window of Popup discussed in the async section. - -Just like a print statement, you can pass any number of arguments you wish. They will all be turned into strings and displayed in the popup window. - -There are a number of Popup output calls, each with a slightly different look (e.g. different button labels). - -The list of Popup output functions are - - Popup - PopupOk - PopupYesNo - PopupCancel - PopupOkCancel - PopupError - PopupTimed, PopupAutoClose - PopupNoWait, PopupNonBlocking - -The trailing portion of the function name after Popup indicates what buttons are shown. `PopupYesNo` shows a pair of button with Yes and No on them. `PopupCancel` has a Cancel button, etc. - -While these are "output" windows, they do collect input in the form of buttons. The Popup functions return the button that was clicked. If the Ok button was clicked, then Popup returns the string 'Ok'. If the user clicked the X button to close the window, then the button value returned is `None`. - -The function `PopupTimed` or `PopupAutoClose` are popup windows that will automatically close after come period of time. - -Here is a quick-reference showing how the Popup calls look. - - sg.Popup('Popup') - Shows OK button sg.PopupOk('PopupOk') - Shows OK button sg.PopupYesNo('PopupYesNo') - Shows Yes and No buttons @@ -477,42 +479,42 @@ Here is a quick-reference showing how the Popup calls look. sg.PopupError('PopupError') - Shows red error button sg.PopupTimed('PopupTimed') - Automatically closes sg.PopupAutoClose('PopupAutoClose') - Same as PopupTimed - - - -![snap0256](https://user-images.githubusercontent.com/13696193/44957394-1380ab00-aea0-11e8-98b1-1ab7d7bd5b37.jpg) - -![snap0257](https://user-images.githubusercontent.com/13696193/44957400-167b9b80-aea0-11e8-9d42-2314f24e62de.jpg) - -![snap0258](https://user-images.githubusercontent.com/13696193/44957399-154a6e80-aea0-11e8-9580-e716f839d400.jpg) - -![snap0259](https://user-images.githubusercontent.com/13696193/44957398-14b1d800-aea0-11e8-9e88-c2b36a248447.jpg) - -![snap0260](https://user-images.githubusercontent.com/13696193/44957397-14b1d800-aea0-11e8-950b-6d0b4f33841a.jpg) - -![snap0261](https://user-images.githubusercontent.com/13696193/44957396-14194180-aea0-11e8-8eef-bb2e1193ecfa.jpg) - -![snap0264](https://user-images.githubusercontent.com/13696193/44957595-9e15da00-aea1-11e8-8909-6b6121b74509.jpg) - + + + +![snap0256](https://user-images.githubusercontent.com/13696193/44957394-1380ab00-aea0-11e8-98b1-1ab7d7bd5b37.jpg) + +![snap0257](https://user-images.githubusercontent.com/13696193/44957400-167b9b80-aea0-11e8-9d42-2314f24e62de.jpg) + +![snap0258](https://user-images.githubusercontent.com/13696193/44957399-154a6e80-aea0-11e8-9580-e716f839d400.jpg) + +![snap0259](https://user-images.githubusercontent.com/13696193/44957398-14b1d800-aea0-11e8-9e88-c2b36a248447.jpg) + +![snap0260](https://user-images.githubusercontent.com/13696193/44957397-14b1d800-aea0-11e8-950b-6d0b4f33841a.jpg) + +![snap0261](https://user-images.githubusercontent.com/13696193/44957396-14194180-aea0-11e8-8eef-bb2e1193ecfa.jpg) + +![snap0264](https://user-images.githubusercontent.com/13696193/44957595-9e15da00-aea1-11e8-8909-6b6121b74509.jpg) + Popup(*args, Variable number of arguments you want to display - button_color=None, Color of buttons (text_color, background_color) - background_color=None, Color of background - text_color=None, Color of text - button_type=POPUP_BUTTONS_OK, Type of button layout - auto_close=False, If True window will automatically close - auto_close_duration=None, Number of seconds for autoclose - non_blocking=False, If True returns immediately - icon=DEFAULT_WINDOW_ICON, Icon to use on the taskbar - line_width=None, Width of lines in characters - font=None, Font to use for characters - no_titlebar=False, If True no titlebar will be shown - grab_anywhere=False, If True can move window by grabbing anywhere - keep_on_top=False, If True window will be on top of other windows - location=(None,None)): (x,y) coordinates to show the window + button_color=None, Color of buttons (text_color, background_color) + background_color=None, Color of background + text_color=None, Color of text + button_type=POPUP_BUTTONS_OK, Type of button layout + auto_close=False, If True window will automatically close + auto_close_duration=None, Number of seconds for autoclose + non_blocking=False, If True returns immediately + icon=DEFAULT_WINDOW_ICON, Icon to use on the taskbar + line_width=None, Width of lines in characters + font=None, Font to use for characters + no_titlebar=False, If True no titlebar will be shown + grab_anywhere=False, If True can move window by grabbing anywhere + keep_on_top=False, If True window will be on top of other windows + location=(None,None)): (x,y) coordinates to show the window -The other output Popups are variations on parameters. Usually the button_type parameter is the primary one changed. +The other output Popups are variations on parameters. Usually the button_type parameter is the primary one changed. The choices for button_type are: ``` @@ -526,89 +528,89 @@ 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 PopupYesNo 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. - - sg.PopupScrolled(my_text) - -![scrolledtextbox 2](https://user-images.githubusercontent.com/13696193/43667324-712aa0d4-9745-11e8-83a9-a0d0570d0865.jpg) - - -The `PopupScrolled` 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.PopupScrolled(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. - -### PopupNoWait - -The Popup call PopupNoWait or PopupNonBlocking will create a popup window and then immediately return control back to you. All other popup functions will block, waiting for the user to close the popup window. - +#### Scrolled Output +There is a scrolled version of Popups should you have a lot of information to display. + + sg.PopupScrolled(my_text) + +![scrolledtextbox 2](https://user-images.githubusercontent.com/13696193/43667324-712aa0d4-9745-11e8-83a9-a0d0570d0865.jpg) + + +The `PopupScrolled` 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.PopupScrolled(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. + +### PopupNoWait + +The Popup call PopupNoWait or PopupNonBlocking will create a popup window and then immediately return control back to you. All other popup functions will block, waiting for the user to close the popup window. + 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 Input - + + + +## Popup Input + There are Popup calls for single-item inputs. These follow the pattern of `Popup` followed by `Get` and then the type of item to get. There are 3 of these input Popups to choose from, each with settings enabling customization. - - - `PopupGetString` - get a single line of text - - `PopupGetFile` - get a filename - - `PopupGetFolder` - get a folder name - + + - `PopupGetString` - get a single line of text + - `PopupGetFile` - get a filename + - `PopupGetFolder` - 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. - -### PopupGetText + +### PopupGetText Use this Popup to get a ssingle line of text from the user. ``` PopupGetText(message,The message you wish to display with the input field - default_text='', Text to initially fill into the input field - password_char='', Passwork character if this is a password field - size=(None,None), Size of the window - button_color=None, Color to use for buttons (foreground, background) - background_color=None, Background color for window - text_color=None, Text color for window - icon=DEFAULT_WINDOW_ICON, Icon to display on taskbar - font=None, Font to use for text - no_titlebar=False, If True no titlebar will be shown - grab_anywhere=False, If True can grab anywhere to move the window - keep_on_top=False, If True window will stay on top of other windows - location=(None,None)) Location on screen to display window -``` + default_text='', Text to initially fill into the input field + password_char='', Passwork character if this is a password field + size=(None,None), Size of the window + button_color=None, Color to use for buttons (foreground, background) + background_color=None, Background color for window + text_color=None, Text color for window + icon=DEFAULT_WINDOW_ICON, Icon to display on taskbar + font=None, Font to use for text + no_titlebar=False, If True no titlebar will be shown + grab_anywhere=False, If True can grab anywhere to move the window + keep_on_top=False, If True window will stay on top of other windows + location=(None,None)) Location on screen to display window +``` - import PySimpleGUI as sg - - text = sg.PopupGetText('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) - -### PopupGetFile + import PySimpleGUI as sg + + text = sg.PopupGetText('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) + +### PopupGetFile Gets a filename from the user. There are options to configure the type of dialog box to show. Normally an "Open File" dialog box is shown ``` PopupGetFile(message, Message to show in the window - default_path='', Path browsing should start from - default_extension='', Which filetype is the default - save_as=False, Determines which dialog box stype to show - file_types=(("ALL Files", "*.*"),), Which filetypes are displayed - no_window=False, if True no window is displayed except the dialog box - size=(None,None), Size of window - button_color=None, Color of buttons - background_color=None, Color of window background - text_color=None, Color of text in window - icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar - font=None, Font to use - no_titlebar=False, If True does not display a titlebar - grab_anywhere=False, if True can grab window anywhere to move it - keep_on_top=False, if True window will be on top of others - location=(None,None)) Location on screen to show window + default_path='', Path browsing should start from + default_extension='', Which filetype is the default + save_as=False, Determines which dialog box stype to show + file_types=(("ALL Files", "*.*"),), Which filetypes are displayed + no_window=False, if True no window is displayed except the dialog box + size=(None,None), Size of window + button_color=None, Color of buttons + background_color=None, Color of window background + text_color=None, Color of text in window + icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar + font=None, Font to use + no_titlebar=False, If True does not display a titlebar + grab_anywhere=False, if True can grab window anywhere to move it + keep_on_top=False, if True window will be on top of others + location=(None,None)) Location on screen to show window ``` If configured as an Open File Popup then (save_as is not True) the dialog box will look like this @@ -626,618 +628,668 @@ If you choose a filename that already exists, you'll get a warning popup box ask A typical call produces this window. - text = sg.PopupGetFile('Please enter a file name') - sg.Popup('Results', 'The value returned from PopupGetFile', text) - -![popupgetfile](https://user-images.githubusercontent.com/13696193/44957857-2fd31680-aea5-11e8-8eb7-f6b91c202cc8.jpg) - + text = sg.PopupGetFile('Please enter a file name') + sg.Popup('Results', 'The value returned from PopupGetFile', text) + +![popupgetfile](https://user-images.githubusercontent.com/13696193/44957857-2fd31680-aea5-11e8-8eb7-f6b91c202cc8.jpg) + ### PopupGetFolder -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. `PopupGetFile` shows an Open File dialog box while `PopupGetFolder` shows an Open Folder dialog box. +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. `PopupGetFile` shows an Open File dialog box while `PopupGetFolder` shows an Open Folder dialog box. ``` PopupGetFolder(message, Message to display in window - default_path='', Path to start browsing - no_window=False, If True no window will be shown - size=(None,None), Size of window - button_color=None, Color of buttons - background_color=None, Background color of window - text_color=None, Color of window text - icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar - font=None, Font to use for window - no_titlebar=False, If True no titlebar will be shown - grab_anywhere=False, If True can grab anywhere on window to move - keep_on_top=False, If True window will be on top - location=(None, None)) Location on screen to create window + default_path='', Path to start browsing + no_window=False, If True no window will be shown + size=(None,None), Size of window + button_color=None, Color of buttons + background_color=None, Background color of window + text_color=None, Color of window text + icon=DEFAULT_WINDOW_ICON, Icon to show on taskbar + font=None, Font to use for window + no_titlebar=False, If True no titlebar will be shown + grab_anywhere=False, If True can grab anywhere on window to move + keep_on_top=False, If True window will be on top + location=(None, None)) Location on screen to create window ``` This is a typpical call - - text = sg.PopupGetFolder('Please enter a folder name') - sg.Popup('Results', 'The value returned from PopupGetFolder', text) - -![popupgetfolder](https://user-images.githubusercontent.com/13696193/44957861-45484080-aea5-11e8-926c-cf607a45251c.jpg) - + + text = sg.PopupGetFolder('Please enter a folder name') + sg.Popup('Results', 'The value returned from PopupGetFolder', text) + +![popupgetfolder](https://user-images.githubusercontent.com/13696193/44957861-45484080-aea5-11e8-926c-cf607a45251c.jpg) -## 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? - - - OneLineProgressMeter(title, - current_value, - max_value, - key, - *args, - orientation=None, - bar_color=DEFAULT_PROGRESS_BAR_COLOR, - button_color=None, - size=DEFAULT_PROGRESS_BAR_SIZE, - border_width=DEFAULT_PROGRESS_BAR_BORDER_WIDTH): - -Here's the one-line Progress Meter in action! - - for i in range(1,10000): - sg.OneLineProgressMeter('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 `OneLineProgressMeter`. 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 -Another call in the 'Easy' families of APIs is `EasyPrint`. It will 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 a 'print' call in your code. You can even replace your 'print' calls with calls to EasyPrint by simply sticking the statement - - print = sg.EasyPrint - -at the top of your code. -There are a number of names for the same EasyPrint function. `Print` is one of the better ones to use as it's easy to remember. It is simply `print` with a capital P. - - 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: - - import PySimpleGUI as sg - - print=sg.Print - for i in range(100): - print(i) - -Just like the standard print call, `EasyPrint` supports the `sep` and `end` keyword arguments. Other names that can be used to call `EasyPrint` include `Print`, `eprint`, If you want to close the window, call the function `EasyPrintClose`. - -You can change the size of the debug window using the `SetOptions` call with the `debug_win_size` parameter. +## 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? + + + OneLineProgressMeter(title, + current_value, + max_value, + key, + *args, + orientation=None, + bar_color=DEFAULT_PROGRESS_BAR_COLOR, + button_color=None, + size=DEFAULT_PROGRESS_BAR_SIZE, + border_width=DEFAULT_PROGRESS_BAR_BORDER_WIDTH): + +Here's the one-line Progress Meter in action! + + for i in range(1,10000): + sg.OneLineProgressMeter('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 `OneLineProgressMeter`. 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 +Another call in the 'Easy' families of APIs is `EasyPrint`. It will 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 a 'print' call in your code. You can even replace your 'print' calls with calls to EasyPrint by simply sticking the statement + + print = sg.EasyPrint + +at the top of your code. +There are a number of names for the same EasyPrint function. `Print` is one of the better ones to use as it's easy to remember. It is simply `print` with a capital P. + + 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: + + import PySimpleGUI as sg + + print=sg.Print + for i in range(100): + print(i) + +Just like the standard print call, `EasyPrint` supports the `sep` and `end` keyword arguments. Other names that can be used to call `EasyPrint` include `Print`, `eprint`, If you want to close the window, call the function `EasyPrintClose`. + +You can change the size of the debug window using the `SetOptions` call with the `debug_win_size` parameter. *A word of caution.* There are known problems when multiple PySimpleGUI windows are opened. If you open one of these debug windows, if you close it using the Quit button, it can have the side-effect of causing other visible windows to also close. It's a known architectural issue. - ---- -# 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-persistant 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. When this happens, the window will be automatically closed. - -Two other types of windows exist. -1. Persistent window - rather than closing on button clicks, the show window function returns and the window continues to be visible. This is good for applications like a chat window. -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. - -It's both not enjoyable nor helpful to immediately jump into tweaking each and every little thing available to you. - -## 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. - - import PySimpleGUI as sg - - layout = [[sg.Text('Enter a Number')], - [sg.Input()], - [sg.OK()] ] - - button, (number,) = sg.Window('Enter a number example').Layout(layout).Read() - - sg.Popup(button, number) - -### 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. - - import PySimpleGUI as sg - - layout = [[sg.Text('Filename')], - [sg.Input(), sg.FileBrowse()], - [sg.OK(), sg.Cancel()] ] - - button, (number,) = sg.Window('Get filename example').Layout(layout).Read() - - sg.Popup(button, number) - - -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. - - +--- +# 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-persistant 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. When this happens, the window will be automatically closed. + +Two other types of windows exist. +1. Persistent window - rather than closing on button clicks, the show window function returns and the window continues to be visible. This is good for applications like a chat window. +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. + +It's both not enjoyable nor helpful to immediately jump into tweaking each and every little thing available to you. + +## 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. + + import PySimpleGUI as sg + + layout = [[sg.Text('Enter a Number')], + [sg.Input()], + [sg.OK()] ] + + event, (number,) = sg.Window('Enter a number example').Layout(layout).Read() + + sg.Popup(event, number) + +### 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. + + import PySimpleGUI as sg + + layout = [[sg.Text('Filename')], + [sg.Input(), sg.FileBrowse()], + [sg.OK(), sg.Cancel()] ] + + event, (number,) = sg.Window('Get filename example').Layout(layout).Read() + + sg.Popup(event, number) + + +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. + + ## Pattern 1 - Read into list or dictionary (**The Most Common** Pattern) - + This will be the most common pattern you'll follow if you are not using an "event loop" (not reading the window multiple times) It's unusual to assign the values returned from the read call directly into user variables. Usually the variables are grouped together into a list or dictionary of multiple return values. ```python -import PySimpleGUI as sg +import PySimpleGUI as sg + +window_rows = [[sg.Text('SHA-1 and SHA-256 Hashes for the file')], + [sg.InputText(), sg.FileBrowse()], + [sg.Submit(), sg.Cancel()]] -window_rows = [[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(window_rows) -button, values = window.Read() +event, values = window.Read() source_filename = values[0] ``` -## Pattern 2 - 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. +## Pattern 2 - 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 - -layout = [[sg.Text('Persistent window')], - [sg.Input()], - [sg.RButton('Read'), sg.Exit()]] - -window = sg.Window('Window that stays open').Layout(layout) - -while True: - button, values = window.Read() - if button is None or button == 'Exit': - break - print(button, values) +import PySimpleGUI as sg + +layout = [[sg.Text('Persistent window')], + [sg.Input()], + [sg.RButton('Read'), sg.Exit()]] + +window = sg.Window('Window that stays open').Layout(layout) + +while True: + event, values = window.Read() + if event is None or event == 'Exit': + break + print(event, values) ``` - - + + ### 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 this goal is debatable, but it was an attempt just the same. - + +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 this goal is debatable, but it was an attempt just the same. + The key to custom windows in PySimpleGUI is to view windows as ROWS of 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 + + Let's dissect this little program ```python - import PySimpleGUI as sg - - 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.FolderBrowse()], - [sg.Submit(), sg.Cancel()]] - - window = sg.Window('Rename Files or Folders') - - button, values = window.Layout(layout).Read() + import PySimpleGUI as sg + + 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.FolderBrowse()], + [sg.Submit(), sg.Cancel()]] + + window = sg.Window('Rename Files or Folders') + + event, values = window.Layout(layout).Read() ``` - - -![snap0131](https://user-images.githubusercontent.com/13696193/43417007-df6d8408-9407-11e8-9986-30f0415f08a5.jpg) - -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: - - 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.FolderBrowse()], - [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 list with 2 values in it. + + +![snap0131](https://user-images.githubusercontent.com/13696193/43417007-df6d8408-9407-11e8-9986-30f0415f08a5.jpg) + +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: + + 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.FolderBrowse()], + [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 list with 2 values in it. ```python - button, values = window.Read() + event, values = window.Read() folder_path, file_path = values ``` - + In one statement we both show the window and read the user's inputs. In the next the *list* 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 - - As of version 2.8 there are 2 forms of return values, list and dictionary. - -### Return values as a list - - By default return values are a list of values, one entry for each input field. - - Return information from Window, PSG's primary window builder interface, is in this format: - - button, (value1, value2, ...) - -Each of the Elements that are Input Elements will have a value in the list of return values. You can unpack your GUI directly into the variables you want to use. - - button, (filename, folder1, folder2, should_overwrite) = sg.Window('My title').Layout(window_rows).Read() - - Or, more commonly, you can unpack the return results separately. - + + +## Return values + + As of version 2.8 there are 2 forms of return values, list and dictionary. + +### Two Return Values + +All Window Read and ReadNonBlocking calls return 2 values. By convention a read statement is written: ```python -button, values = sg.Window('My title').Layout(window_rows).Read() -button, value_list = window.Layout(window_rows).Read() -value1 = value_list[0] -value2 = value_list[1] - ... -``` - -### Return values as a dictionary - -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: - +event, values = window.Read() +``` +All of the demo programs and the Cookbook recipes have this line of code for windows that are normal reads (not non-blocking). A similar line of code is used for non-blocking window reads: +```python +event, values = window.ReadNonBlocking() +``` + +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 these statements. + +The first parameter `event` describes **why** the read completed. What was the 'event' that caused us to return from reading the window. 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 + + +***Most*** of the time the event will be a button click or the window was closed. + +Another convention to follow is the check for windows being closed with an X. This is an important event to catch. If you don't check for this and you attempt to use the window, your program will crash. Please check for closed window and exit your program gracefully. + +To check for a closed window use this line of code: + + if event is None: + + +### 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. + +Each of the Elements that are Input Elements will have a value in the list of return values. You can unpack your GUI directly into the variables you want to use. + + event, (filename, folder1, folder2, should_overwrite) = sg.Window('My title').Layout(window_rows).Read() + + Or, more commonly, you can unpack the return results separately. + +```python +event, values = sg.Window('My title').Layout(window_rows).Read() +event, value_list = window.Layout(window_rows).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 / advanced method is to request your values be returned as a dictionary. + +### Return values as a dictionary + +For those of you that have not encountered a Python dictionary, don't freak out! Just copy and paste this 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(layout).Read() - -All of your return values will be stored in the variable `values`. When using the dictionary return values, the `values` variable is a dictionary. - - 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. - - import PySimpleGUI as sg - window = sg.Window('Simple data entry window') - 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()] - ] - - button, values = window.Layout(layout).Read() - - sg.Popup(button, 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'] - -You will find the key field used quite heavily in most PySimpleGUI windows unless the window is very simple. - -### Button Return Values - -The button value from a Read call will be one of 3 values: -1. The Button's text -2. The Button's key -3. None - -If a button has a key set for it when it's created, then that key will be returned. If no key is set, then the button text is returned. If no button was clicked, but the window returned anyway, the button value is None. - -None is returned when the user clicks the X to close a window. - -If your window has an event loop where it is read over and over, remember to give your user an "out". You should always check for a None value and it's a good practice to provide an Exit button of some kind. Thus design patterns often resemble this Event Loop: - - while True: - button, values = window.Read() - if button is None or button == 'Quit': - break - -## 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. + + + 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. + + import PySimpleGUI as sg + window = sg.Window('Simple data entry window') + 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()] + ] + + event, values = window.Layout(layout).Read() + + 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. + +Another convention you'll see in some of the demo programs is keys being named with an underscore 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_'` + + + +### Button Event Return Values + +The button value from a Read call will be one of 3 values: +1. The Button's text +2. The Button's key + +If a button has a key set for it when it's created, then that key will be returned. If no key is set, then the button text is returned. If no button was clicked, but the window returned anyway, the button value is None. + +None is returned when the user clicks the X to close a window. + +If your window has an event loop where it is read over and over, remember to give your user an "out". You should always check for a None value and it's a good practice to provide an Exit button of some kind. Thus design patterns often resemble this Event Loop: + + while True: + event, values = window.Read() + if event is None or event == 'Quit': + break + +## 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 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. - -Let's take a Pi demo program as an example. This program shows a GUI window, gets button presses, and uses them to control some LEDs. It loops, reading user input and doing something with it. - -This little program has a typical 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. + +Let's take a Pi demo program as an example. This program shows a GUI window, gets button presses, and uses them to control some LEDs. It loops, reading user input and doing something with it. + +This little program has a typical Event Loop + ![readme example](https://user-images.githubusercontent.com/13696193/46566965-f4d65f80-c8f6-11e8-91a3-8cebad0cba90.jpg) - - - + + + ```python -import PySimpleGUI as sg - -layout = [[sg.Text('Click read to read the input value')], - [sg.Input()], - [sg.RButton('Read'), sg.Exit()]] - -window = sg.Window('Persistent GUI Window').Layout(layout) - -while True: - button, values = window.Read() - if button is None or button == 'Exit': - break - print(button, values) +import PySimpleGUI as sg + +layout = [[sg.Text('Click read to read the input value')], + [sg.Input()], + [sg.RButton('Read'), sg.Exit()]] + +window = sg.Window('Persistent GUI Window').Layout(layout) + +while True: + event, values = window.Read() + if event is None or event == 'Exit': + break + print(event, values) ``` - -In the Event Loop we are reading the window and then doing a series of button compares to determine what to do based on the button that was clicks (value of `button` variable) - + +In the Event Loop we are reading the window and then doing a series of button compares to determine what to do based on the button that was clicks (value of `button` variable) + The way buttons are presented to the caller in PySimpleGUI is ***not*** how *most* GUI frameworks handle button clicks. Most GUI frameworks, including tkinter, use ***callback*** functions, a function you define would be called when a button is clicked. This requires you to write asynchronous code, a concept beginners often stumble on and one that presents a barrier. - -There is a more communications that have to happen between parts of your program when using callbacks. Callbacks break apart your program's logic apart and scatter it. One of the larger hurdles for beginners to GUI programming are these callback functions. - -PySimpleGUI was specifically designed in a way so that callbacks would not be required. There is no coordination between one function and another required. You simply read your button click and take appropriate action at the same location in the code as when you read the button value. - -Whether or not this is a "proper" design for GUI programs can be debated. It's not a terrible trade-off to run your own event loop and having a functioning GUI application versus one that maybe never gets written because callback functions were too much to grasp. - - --- - -## All Widgets / Elements - + +There is a more communications that have to happen between parts of your program when using callbacks. Callbacks break apart your program's logic apart and scatter it. One of the larger hurdles for beginners to GUI programming are these callback functions. + +PySimpleGUI was specifically designed in a way so that callbacks would not be required. There is no coordination between one function and another required. You simply read your button click and take appropriate action at the same location in the code as when you read the button value. + +Whether or not this is a "proper" design for GUI programs can be debated. It's not a terrible trade-off to run your own event loop and having a functioning GUI application versus one that maybe never gets written because callback functions were too much to grasp. + + --- + +## All Widgets / Elements + This code utilizes many of the common Elements. It does not include Tabs/Tab Groups. - - import PySimpleGUI as sg - - sg.ChangeLookAndFeel('GreenTan') - - # ------ Menu Definition ------ # - menu_def = [['File', ['Open', 'Save', 'Exit', 'Properties']], - ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], - ['Help', 'About...'], ] - - # ------ Column Definition ------ # - column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')], - [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]] - - layout = [ - [sg.Menu(menu_def, tearoff=True)], - [sg.Text('All graphic widgets in one window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)], - [sg.Text('Here is some text.... and a place to enter text')], - [sg.InputText('This is my text')], - [sg.Frame(layout=[ - [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)], - [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')], - [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)), - sg.Multiline(default_text='A second multi-line', size=(35, 3))], - [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)), - sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)], - [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))], - [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)), - sg.Frame('Labelled Group',[[ - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25), - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75), - sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10), - sg.Column(column1, background_color='#F7F3EC')]])], - [sg.Text('_' * 80)], - [sg.Text('Choose A Folder', size=(35, 1))], - [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'), - sg.InputText('Default Folder'), sg.FolderBrowse()], - [sg.Submit(tooltip='Click to submit this window'), sg.Cancel()] - ] - - - window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout) - - button, values = window.Read() - - sg.Popup('Title', - 'The results of the window.', - 'The button clicked was "{}"'.format(button), - 'The values are', values) - -This is a somewhat complex window with quite a bit of custom sizing to make things line up well. This is code you only have to write once. When looking at the code, remember that what you're seeing is a list of lists. Each row contains a list of Graphical Elements that are used to create the window. - - ![everything bagel](https://user-images.githubusercontent.com/13696193/45914128-87163800-be0e-11e8-9a83-7ee5960e88b9.jpg) - -Clicking the Submit button caused the window call to return. The call to Popup resulted in this window. - -![everything bagel reseults](https://user-images.githubusercontent.com/13696193/45914129-87aece80-be0e-11e8-8aae-9a483a9ad4a6.jpg) - - +```python + import PySimpleGUI as sg + + sg.ChangeLookAndFeel('GreenTan') + + # ------ Menu Definition ------ # + menu_def = [['File', ['Open', 'Save', 'Exit', 'Properties']], + ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ], + ['Help', 'About...'], ] + + # ------ Column Definition ------ # + column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')], + [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]] + + layout = [ + [sg.Menu(menu_def, tearoff=True)], + [sg.Text('All graphic widgets in one window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)], + [sg.Text('Here is some text.... and a place to enter text')], + [sg.InputText('This is my text')], + [sg.Frame(layout=[ + [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)], + [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')], + [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)), + sg.Multiline(default_text='A second multi-line', size=(35, 3))], + [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)), + sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)], + [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))], + [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)), + sg.Frame('Labelled Group',[[ + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25), + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75), + sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10), + sg.Column(column1, background_color='#F7F3EC')]])], + [sg.Text('_' * 80)], + [sg.Text('Choose A Folder', size=(35, 1))], + [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'), + sg.InputText('Default Folder'), sg.FolderBrowse()], + [sg.Submit(tooltip='Click to submit this window'), sg.Cancel()] + ] + + + window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout) + + event, values = window.Read() + + sg.Popup('Title', + 'The results of the window.', + 'The button clicked was "{}"'.format(event), + 'The values are', values) +``` +This is a somewhat complex window with quite a bit of custom sizing to make things line up well. This is code you only have to write once. When looking at the code, remember that what you're seeing is a list of lists. Each row contains a list of Graphical Elements that are used to create the window. + + ![everything bagel](https://user-images.githubusercontent.com/13696193/45914128-87163800-be0e-11e8-9a83-7ee5960e88b9.jpg) + +Clicking the Submit button caused the window call to return. The call to Popup resulted in this window. + +![everything bagel reseults](https://user-images.githubusercontent.com/13696193/45914129-87aece80-be0e-11e8-8aae-9a483a9ad4a6.jpg) + + **`Note, button value can be None`**. The value for `button` will be the text that is displayed on the button element when it was created. If the user closed the window using something other than a button, then `button` will be `None`. It is ***vitally*** ***important*** that your code contain the proper checks for None. Always give your users a way out of the window. Otherwise you'll end up with windows that never properly close. - -You can see in the results Popup window that the values returned are a list. Each input field in the window generates one item in the return values list. All input fields return a `string` except for Check Boxes and Radio Buttons. These return `bool`. - ---- -# 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 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 can see in the results Popup window that the values returned are a list. Each input field in the window generates one item in the return values list. All input fields return a `string` except for Check Boxes and Radio Buttons. These return `bool`. + +--- +# 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 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. The call to look for that will show you non-blocking windows are calls to `ReadNonBlocking()`. You can read more about Async windows at the end of this document. - -# Window Object - Beginning a window -The first step is to create the window object using the desired window customization. - - with Window('Everything bagel', auto_size_text=True, default_element_size=(30,1)) as window: - -This is the definition of the Window object: - - def Window(title, - default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), - default_button_element_size = (None, None), - auto_size_text=None, - auto_size_buttons=None, - location=(None, None), - font=None, - button_color=None,Font=None, - progress_bar_color=(None,None), - background_color=None - border_depth=None, - auto_close=False, - auto_close_duration=DEFAULT_AUTOCLOSE_TIME, - icon=DEFAULT_WINDOW_ICON, - force_toplevel=False - return_keyboard_events=False, - use_default_focus=True, - text_justification=None, - no_titlebar=False, - grab_anywhere=False - keep_on_top=False): - - -Parameter Descriptions. You will find these same parameters specified for each `Element` and some of them in `Row` specifications. The `Element` specified value will take precedence over the `Row` and `window` values. - - default_element_size - Size of elements in window in characters (width, height) - default_button_element_size - Size of buttons on this window - auto_size_text - Bool. True if elements should size themselves according to contents. Defaults to True - auto_size_buttons - Bool. True if button elements should size themselves according to their text label - location - (x,y) Location to place window in pixels - font - Font name and size for elements of the window - button_color - Default color for buttons (foreground, background). Can be text or hex - progress_bar_color - Foreground and background colors for progress bars - background_color - Color of the window background - border_depth - Amount of 'bezel' to put on input boxes, buttons, etc. - auto_close - Bool. If True window will autoclose - auto_close_duration - Duration in seconds before window closes - icon - .ICO file that will appear on the Task Bar and end of Title Bar - force_top_level - Bool. If set causes a tk.Tk window to be used as primary window rather than tk.TopLevel. Used to get around Matplotlib problem - return_keyboard_events - if True key presses are returned as buttons - use_default_focus - if True and no focus set, then automatically set a focus - text_justification - Justification to use for Text Elements in this window - no_titlebar - Create window without a titlebar - grab_anywhere - Grab any location on the window to move the window - keep_on_top - if True then window will always stop on top of other windows on the screen. Great for floating toolbars. - - -### 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. - -### Sizes -Note several variables that deal with "size". Element sizes are measured in characters. A Text Element with a size of 20,1 has a size of 20 characters wide by 1 character tall. - -The default Element size for PySimpleGUI is `(45,1)`. - -Sizes can be set at the element level, or in this case, the size variables apply to all elements in the window. Setting `size=(20,1)` in the window creation call will set all elements in the window to that size. - -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. - -### Window Methods (things you can do with a Window object) - -There are a few methods (functions) that you will see in this document that act on Windows. The ones you will primarily be calling are: - - window.Layout(layout) - Turns your definition of the Window into Window - window.Finalize() - creates the tkinter objects for the Window. Normally you do not call this - window.Read() - Read the Windows values and get the button / key that caused the Read to return - window.ReadNonBlocking() - Same as Read but will return right away - window.Refresh() - Use if updating elements and want to show the updates prior to the nex Read - window.Fill(values_dict) - Fill each Element with entry from the dictionary passed in - window.SaveToDisk(filename) - Save the Window's values to disk - window.LoadFromDisk(filename) - Load the Window's values from disk - window.CloseNonBlocking() - When done, for good, reading a non-blocking window - window.Disable() - Use to disable the window inpurt when opening another window on top of the primnary Window - window.Enable() - Re-enable a Disabled window - window.FindElement(key) - Returns the element that has a matching key value - -## Window Methods +# Window Object - Beginning a window +The first step is to create the window object using the desired window customization. + + with Window('Everything bagel', auto_size_text=True, default_element_size=(30,1)) as window: + +This is the definition of the Window object: + + def Window(title, + default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), + default_button_element_size = (None, None), + auto_size_text=None, + auto_size_buttons=None, + location=(None, None), + font=None, + button_color=None,Font=None, + progress_bar_color=(None,None), + background_color=None + border_depth=None, + auto_close=False, + auto_close_duration=DEFAULT_AUTOCLOSE_TIME, + icon=DEFAULT_WINDOW_ICON, + force_toplevel=False + return_keyboard_events=False, + use_default_focus=True, + text_justification=None, + no_titlebar=False, + grab_anywhere=False + keep_on_top=False): + + + +Parameter Descriptions. You will find these same parameters specified for each `Element` and some of them in `Row` specifications. The `Element` specified value will take precedence over the `Row` and `window` values. + + default_element_size - Size of elements in window in characters (width, height) + default_button_element_size - Size of buttons on this window + auto_size_text - Bool. True if elements should size themselves according to contents. Defaults to True + auto_size_buttons - Bool. True if button elements should size themselves according to their text label + location - (x,y) Location to place window in pixels + font - Font name and size for elements of the window + button_color - Default color for buttons (foreground, background). Can be text or hex + progress_bar_color - Foreground and background colors for progress bars + background_color - Color of the window background + border_depth - Amount of 'bezel' to put on input boxes, buttons, etc. + auto_close - Bool. If True window will autoclose + auto_close_duration - Duration in seconds before window closes + icon - .ICO file that will appear on the Task Bar and end of Title Bar + force_top_level - Bool. If set causes a tk.Tk window to be used as primary window rather than tk.TopLevel. Used to get around Matplotlib problem + return_keyboard_events - if True key presses are returned as buttons + use_default_focus - if True and no focus set, then automatically set a focus + text_justification - Justification to use for Text Elements in this window + no_titlebar - Create window without a titlebar + grab_anywhere - Grab any location on the window to move the window + keep_on_top - if True then window will always stop on top of other windows on the screen. Great for floating toolbars. + + +### 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. + +### Sizes +Note several variables that deal with "size". Element sizes are measured in characters. A Text Element with a size of 20,1 has a size of 20 characters wide by 1 character tall. + +The default Element size for PySimpleGUI is `(45,1)`. + +Sizes can be set at the element level, or in this case, the size variables apply to all elements in the window. Setting `size=(20,1)` in the window creation call will set all elements in the window to that size. + +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. + +### Window Methods (things you can do with a Window object) + +There are a few methods (functions) that you will see in this document that act on Windows. The ones you will primarily be calling are: + + window.Layout(layout) - Turns your definition of the Window into Window + window.Finalize() - creates the tkinter objects for the Window. Normally you do not call this + window.Read() - Read the Windows values and get the button / key that caused the Read to return + window.ReadNonBlocking() - Same as Read but will return right away + window.Refresh() - Use if updating elements and want to show the updates prior to the nex Read + window.Fill(values_dict) - Fill each Element with entry from the dictionary passed in + window.SaveToDisk(filename) - Save the Window's values to disk + window.LoadFromDisk(filename) - Load the Window's values from disk + window.CloseNonBlocking() - When done, for good, reading a non-blocking window + window.Disable() - Use to disable the window inpurt when opening another window on top of the primnary Window + window.Enable() - Re-enable a Disabled window + window.FindElement(key) - Returns the element that has a matching key value + +## Window Methods There are a number of operations you can do on a window after you've created the window. You call these after creating your Windows object. @@ -1255,7 +1307,7 @@ Call to force a window to go through the final stages of initialization. This w #### Read() Read the Window's input values and button clicks in a blocking-fashion -Returns button, values +Returns event, values #### ReadNonBlocking() @@ -1265,7 +1317,7 @@ Read the Window's input values and button clicks but without blocking. It will Cause changes to the window to be displayed on the screen. Normally not needed unless the changes are immediately required or if it's going to be a while before another call to Read. #### Fill(values_dict) -Populates the windows fields with the values shown in the dictionary. +Populates the windows fields with the values shown in the dictionary. #### FindElement(key) @@ -1313,44 +1365,44 @@ Makes a window reappear that was previously made to disappear using Disappear() - -# Elements -"Elements" are the building blocks used to create windows. Some GUI APIs use the term "Widget" to describe these graphic elements. - - Text - Single Line Input - Buttons including these types: - File Browse - Folder Browse - Calendar picker - Date Chooser - Read window - Close window - Realtime - Checkboxes - Radio Buttons - Listbox - Slider - Multi-line Text Input - Scroll-able Output - Progress Bar - Option Menu - Menu - Frame - Column - Graph - Image - Table + +# Elements +"Elements" are the building blocks used to create windows. Some GUI APIs use the term "Widget" to describe these graphic elements. + + Text + Single Line Input + Buttons including these types: + File Browse + Folder Browse + Calendar picker + Date Chooser + Read window + Close window + Realtime + Checkboxes + Radio Buttons + Listbox + Slider + Multi-line Text Input + Scroll-able Output + Progress Bar + Option Menu + Menu + Frame + Column + Graph + Image + Table Tree - Tab, TabGroup - Async/Non-Blocking Windows - Tabbed windows - Persistent Windows - Redirect Python Output/Errors to scrolling Window - "Higher level" APIs (e.g. MessageBox, YesNobox, ...) - -## Common Element Parameters -Some parameters that you will see on almost all Elements are: + Tab, TabGroup + Async/Non-Blocking Windows + Tabbed windows + Persistent Windows + Redirect Python Output/Errors to scrolling Window + "Higher level" APIs (e.g. MessageBox, YesNobox, ...) + +## Common Element Parameters +Some parameters that you will see on almost all Elements are: - key - Used with window.FindElement and with return values - tooltip - Hover your mouse over the elemnt and you'll get a popup with this text @@ -1359,14 +1411,14 @@ Some parameters that you will see on almost all Elements are: - colors - Color name or #RRGGBB string - pad - Amount of padding to put around element - + #### Tooltip -Tooltips are text boxes that popup next to an element if you hold your mouse over the top of it. If you want to be extra kind to your window's user, then you can create tooltips for them by setting the parameter `tooltip` to some text string. You will need to supply your own line breaks / text wrapping. If you don't want to manually add them, then take a look at the standard library package `textwrap`. - -Tooltips are one of those "polish" items that really dress-up a GUI and show's a level of sophistication. Go ahead, impress people, throw some tooltips into your GUI. - +Tooltips are text boxes that popup next to an element if you hold your mouse over the top of it. If you want to be extra kind to your window's user, then you can create tooltips for them by setting the parameter `tooltip` to some text string. You will need to supply your own line breaks / text wrapping. If you don't want to manually add them, then take a look at the standard library package `textwrap`. + +Tooltips are one of those "polish" items that really dress-up a GUI and show's a level of sophistication. Go ahead, impress people, throw some tooltips into your GUI. + #### Size -Specifies the amount of room reserved for the Element. For elements that are character based, such a Text, it is (# characters, # rows). Sometimes it is a pixel measurement such as the Image element. And sometimes a mix like on the Slider element (characters long by pixels wide). +Specifies the amount of room reserved for the Element. For elements that are character based, such a Text, it is (# characters, # rows). Sometimes it is a pixel measurement such as the Image element. And sometimes a mix like on the Slider element (characters long by pixels wide). #### Colors A string representing color. Anytime colors are involved, you can specify the tkinter color name such as 'lightblue' or an RGB hex value '#RRGGBB'. For buttons, the color parameter is a tuple (text color, background color) @@ -1401,80 +1453,80 @@ If you are going to do anything beyond the basic stuff with your GUI, then you n Keys are a way for you to "tag" an Element with a value that will be used to identify that element. After you put a key in an element's definition, the values returned from Read will use that key to tell you the value. For example, if you have an input field: Input(key='mykey') And your read looks like this: -button, values = Read() +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 below on Updates to understand that usage. - -### Output Elements -Building a window is simply making lists of Elements. Each list is a row in the overall GUI dialog box. The definition looks something like this: - - layout = [ [row 1 element, row 1 element], - [row 2 element, row 2 element, row 2 element] ] -The code is a crude representation of the GUI, laid out in text. - -## Text Element - - layout = [[sg.Text('This is what a Text Element looks like')]] - - ![simple text](https://user-images.githubusercontent.com/13696193/44959877-e9d97b00-aec3-11e8-9d24-b4405ee4a148.jpg) - - -The most basic element is the Text element. It simply displays text. Many of the 'options' that can be set for a Text element are shared by other elements. - - Text(text - size=(None, None) - auto_size_text=None - click_submits=None - relief=None - font=None - text_color=None - background_color=None - justification=None - pad=None - key=None - tooltip=None) - -. - - Text - The text that's displayed - size - Element's size - click_submits - if clicked will cause a read call to return they key value as the button - relief - relief to use around the text - auto_size_text - Bool. Change width to match size of text - font - Font name and size to use - text_color - text color - background_color - background color - justification - Justification for the text. String - 'left', 'right', 'center' - pad - (x,y) amount of padding in pixels to use around element when packing - key - used to identify element. This value will return as button if click_submits True - tooltip - string representing tooltip - -Some commonly used elements have 'shorthand' versions of the functions to make the code more compact. The functions `T` and `Txt` are the same as calling `Text`. - -**Fonts** in PySimpleGUI are always in this format: - - (font_name, point_size) - -The default font setting is - - ("Helvetica", 10) - + +### Output Elements +Building a window is simply making lists of Elements. Each list is a row in the overall GUI dialog box. The definition looks something like this: + + layout = [ [row 1 element, row 1 element], + [row 2 element, row 2 element, row 2 element] ] +The code is a crude representation of the GUI, laid out in text. + +## Text Element + + layout = [[sg.Text('This is what a Text Element looks like')]] + + ![simple text](https://user-images.githubusercontent.com/13696193/44959877-e9d97b00-aec3-11e8-9d24-b4405ee4a148.jpg) + + +The most basic element is the Text element. It simply displays text. Many of the 'options' that can be set for a Text element are shared by other elements. + + Text(text + size=(None, None) + auto_size_text=None + click_submits=None + relief=None + font=None + text_color=None + background_color=None + justification=None + pad=None + key=None + tooltip=None) + +. + + Text - The text that's displayed + size - Element's size + click_submits - if clicked will cause a read call to return they key value as the button + relief - relief to use around the text + auto_size_text - Bool. Change width to match size of text + font - Font name and size to use + text_color - text color + background_color - background color + justification - Justification for the text. String - 'left', 'right', 'center' + pad - (x,y) amount of padding in pixels to use around element when packing + key - used to identify element. This value will return as button if click_submits True + tooltip - string representing tooltip + +Some commonly used elements have 'shorthand' versions of the functions to make the code more compact. The functions `T` and `Txt` are the same as calling `Text`. + +**Fonts** in PySimpleGUI are always in this format: + + (font_name, point_size) + +The default font setting is + + ("Helvetica", 10) + **Color** in PySimpleGUI are in one of two formats - color name or RGB value. - - Individual colors are specified using either the color names as defined in tkinter or an RGB string of this format: - - "#RRGGBB" - -**auto_size_text** -A `True` value for `auto_size_text`, when placed on Text Elements, indicates that the width of the Element should be shrunk do the width of the text. The default setting is True. - - -**Shortcut functions** -The shorthand functions for `Text` are `Txt` and `T` - -### Text Methods + + Individual colors are specified using either the color names as defined in tkinter or an RGB string of this format: + + "#RRGGBB" + +**auto_size_text** +A `True` value for `auto_size_text`, when placed on Text Elements, indicates that the width of the Element should be shrunk do the width of the text. The default setting is True. + + +**Shortcut functions** +The shorthand functions for `Text` are `Txt` and `T` + +### Text Methods #### Update ```python @@ -1486,14 +1538,14 @@ text_color - text color to display font - font to use to display -## Multiline Text Element - - layout = [[sg.Multiline('This is what a Multi-line Text Element looks like', size=(45,5))]] - -![multiline](https://user-images.githubusercontent.com/13696193/44959853-b139a180-aec3-11e8-972f-f52188510c88.jpg) - -This Element doubles as both an input and output Element. - +## Multiline Text Element + + layout = [[sg.Multiline('This is what a Multi-line Text Element looks like', size=(45,5))]] + +![multiline](https://user-images.githubusercontent.com/13696193/44959853-b139a180-aec3-11e8-972f-f52188510c88.jpg) + +This Element doubles as both an input and output Element. + Multiline(default_text='', enter_submits = False, disabled=False, @@ -1507,13 +1559,13 @@ This Element doubles as both an input and output Element. focus=False, pad=None, tooltip=None) -. - - default_text - Text to display in the text box - enter_submits - Bool. If True, pressing Enter key submits window - size - Element's size - auto_size_text - Bool. Change width to match size of text - +. + + default_text - Text to display in the text box + enter_submits - Bool. If True, pressing Enter key submits window + size - Element's size + auto_size_text - Bool. Change width to match size of text + ### Multiline Methods ```python Update(value=None, disabled=None, append=False): @@ -1522,33 +1574,33 @@ value - string to set the text field to disabled - set to True to disable the element append - rather than replacing the current text with new text, add the new text onto the end -## Output Element -Output re-routes `Stdout` to a scrolled text box. It's used with Async windows. More on this later. - - window.AddRow(gg.Output(size=(100,20))) - -![output](https://user-images.githubusercontent.com/13696193/44959863-b72f8280-aec3-11e8-8caa-7bc743149953.jpg) - - Output(size=(None, None)) -. - - size - Size of element (width, height) in characters - -## Input Elements - These make up the majority of the window definition. Optional variables at the Element level override the window level values (e.g. `size` is specified in the Element). All input Elements create an entry in the list of return values. A Text Input Element creates a string in the list of items returned. - -## Text Input Element - - layout = [[sg.InputText('Default text')]] - -![inputtext 2](https://user-images.githubusercontent.com/13696193/44959861-b5fe5580-aec3-11e8-8040-53ec241b5079.jpg) - - +## Output Element +Output re-routes `Stdout` to a scrolled text box. It's used with Async windows. More on this later. + + window.AddRow(gg.Output(size=(100,20))) + +![output](https://user-images.githubusercontent.com/13696193/44959863-b72f8280-aec3-11e8-8caa-7bc743149953.jpg) + + Output(size=(None, None)) +. + + size - Size of element (width, height) in characters + +## Input Elements + These make up the majority of the window definition. Optional variables at the Element level override the window level values (e.g. `size` is specified in the Element). All input Elements create an entry in the list of return values. A Text Input Element creates a string in the list of items returned. + +## Text Input Element + + layout = [[sg.InputText('Default text')]] + +![inputtext 2](https://user-images.githubusercontent.com/13696193/44959861-b5fe5580-aec3-11e8-8040-53ec241b5079.jpg) + + def InputText(default_text ='', size=(None, None), - disabled=False, + disabled=False, auto_size_text=None, - password_char='', + password_char='', justification=None, background_color=None, text_color=None, @@ -1557,30 +1609,30 @@ Output re-routes `Stdout` to a scrolled text box. It's used with Async windows. do_not_clear=False, key=None, focus=False, - pad=None): + pad=None): ''' - -. - - default_text - Text initially shown in the input box - size - (width, height) of element in characters - auto_size_text- Bool. True is element should be sized to fit text + +. + + default_text - Text initially shown in the input box + size - (width, height) of element in characters + auto_size_text- Bool. True is element should be sized to fit text disabled - Bool If True the input is disabled - password_char - Character that will be used to replace each entered character. Setting to a value indicates this field is a password entry field - background_color - color to use for the input field background - text_color - color to use for the typed text - do_not_clear - Bool. Normally windows clear when read, turn off clearing with this flag. - key = Dictionary key to use for return values - focus = Bool. True if this field should capture the focus (moves cursor to this field) - - There are two methods that can be called: - - InputText.Update(new_Value) - sets the input value - Input.Text(Get() - returns the current value of the field. - - -Shorthand functions that are equivalent to `InputText` are `Input` and `In` - + password_char - Character that will be used to replace each entered character. Setting to a value indicates this field is a password entry field + background_color - color to use for the input field background + text_color - color to use for the typed text + do_not_clear - Bool. Normally windows clear when read, turn off clearing with this flag. + key = Dictionary key to use for return values + focus = Bool. True if this field should capture the focus (moves cursor to this field) + + There are two methods that can be called: + + InputText.Update(new_Value) - sets the input value + Input.Text(Get() - returns the current value of the field. + + +Shorthand functions that are equivalent to `InputText` are `Input` and `In` + ### TextInput Methods ```python @@ -1592,40 +1644,40 @@ value - new value to display in field disabled - if True will disable the element Get - Returns the current value for the element (you can get also from a call to Read) - - -## Combo Element -Also known as a drop-down list. Only required parameter is the list of choices. The return value is a string matching what's visible on the GUI. - - layout = [[sg.InputCombo(['choice 1', 'choice 2'])]] - -![combobox](https://user-images.githubusercontent.com/13696193/44959860-b565bf00-aec3-11e8-82fe-dbe41252458b.jpg) - - InputCombo(values, , - default_value=None - size=(None, None) - auto_size_text=None - background_color=None - text_color=None - change_submits=False - disabled=False - key=None - pad=None - tooltip=None -. - - values - Choices to be displayed. List of strings - default_value - which value should be initially chosen - size - (width, height) of element in characters - auto_size_text - Bool. True if size should fit the text length - background_color - color to use for the input field background - text_color - color to use for the typed text - change_submits - Bool. If set causes Read to immediately return if the selected value changes - disabled - Bool. If set will disable changes - key - Dictionary key to use for return values - pad - (x,y) Amount of padding to put around element in pixels - tooltip - Text string. If set, hovering over field will popup the text - + + +## Combo Element +Also known as a drop-down list. Only required parameter is the list of choices. The return value is a string matching what's visible on the GUI. + + layout = [[sg.InputCombo(['choice 1', 'choice 2'])]] + +![combobox](https://user-images.githubusercontent.com/13696193/44959860-b565bf00-aec3-11e8-82fe-dbe41252458b.jpg) + + InputCombo(values, , + default_value=None + size=(None, None) + auto_size_text=None + background_color=None + text_color=None + change_submits=False + disabled=False + key=None + pad=None + tooltip=None +. + + values - Choices to be displayed. List of strings + default_value - which value should be initially chosen + size - (width, height) of element in characters + auto_size_text - Bool. True if size should fit the text length + background_color - color to use for the input field background + text_color - color to use for the typed text + change_submits - Bool. If set causes Read to immediately return if the selected value changes + disabled - Bool. If set will disable changes + key - Dictionary key to use for return values + pad - (x,y) Amount of padding to put around element in pixels + tooltip - Text string. If set, hovering over field will popup the text + Shortcut functions - Combo, DropDown, Drop ### Combo Methods @@ -1637,61 +1689,61 @@ values - change list of choices set_to_index - change selection to a particular choice disable - if True will disable element -## Listbox Element -The standard listbox like you'll find in most GUIs. Note that the return values from this element will be a ***list of results, not a single result***. This is because the user can select more than 1 item from the list (if you set the right mode). - - layout = [[sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6))]] - -![listbox 2](https://user-images.githubusercontent.com/13696193/44959859-b4cd2880-aec3-11e8-881c-1e369d5c6337.jpg) - - - Listbox(values - default_values=None - select_mode=None - change_submits=False - bind_return_key=False - size=(None, None) +## Listbox Element +The standard listbox like you'll find in most GUIs. Note that the return values from this element will be a ***list of results, not a single result***. This is because the user can select more than 1 item from the list (if you set the right mode). + + layout = [[sg.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6))]] + +![listbox 2](https://user-images.githubusercontent.com/13696193/44959859-b4cd2880-aec3-11e8-881c-1e369d5c6337.jpg) + + + Listbox(values + default_values=None + select_mode=None + change_submits=False + bind_return_key=False + size=(None, None) disabled = False, - auto_size_text=None - font=None - background_color=None - text_color=None - key=None - pad=None - tooltip=None): - -. - - values - Choices to be displayed. List of strings - select_mode - Defines how to list is to operate. - Choices include constants or strings: - Constants version: - LISTBOX_SELECT_MODE_BROWSE - LISTBOX_SELECT_MODE_EXTENDED - LISTBOX_SELECT_MODE_MULTIPLE - LISTBOX_SELECT_MODE_SINGLE - the default - Strings version: - 'browse' - 'extended' - 'multiple' - 'single' - change_submits - if True, the window read will return with a button value of '' - bind_return_key - if the focus is on the listbox and the user presses return key, or if the user double clicks an item, then the read will return - size - (width, height) of element in characters + auto_size_text=None + font=None + background_color=None + text_color=None + key=None + pad=None + tooltip=None): + +. + + values - Choices to be displayed. List of strings + select_mode - Defines how to list is to operate. + Choices include constants or strings: + Constants version: + LISTBOX_SELECT_MODE_BROWSE + LISTBOX_SELECT_MODE_EXTENDED + LISTBOX_SELECT_MODE_MULTIPLE + LISTBOX_SELECT_MODE_SINGLE - the default + Strings version: + 'browse' + 'extended' + 'multiple' + 'single' + change_submits - if True, the window read will return with a button value of '' + bind_return_key - if the focus is on the listbox and the user presses return key, or if the user double clicks an item, then the read will return + size - (width, height) of element in characters disapled - Bool. If True element is disabled - auto_size_text - Bool. True if size should fit the text length - background_color - color to use for the input field background - font - font to use for items in list - text_color - color to use for the typed text - key - Dictionary key to use for return values and to find element - pad - amount of padding to use when packing - tooltip - tooltip text - -The `select_mode` option can be a string or a constant value defined as a variable. Generally speaking strings are used for these kinds of options. - -ListBoxes can cause a window to return from a Read call. If the flag change_submits is set, then when a user makes a selection, the Read immediately returns. -Another way ListBoxes can cause Reads to return is if the flag bind_return_key is set. If True, then if the user presses the return key while an entry is selected, then the Read returns. Also, if this flag is set, if the user double-clicks an entry it will return from the Read. - + auto_size_text - Bool. True if size should fit the text length + background_color - color to use for the input field background + font - font to use for items in list + text_color - color to use for the typed text + key - Dictionary key to use for return values and to find element + pad - amount of padding to use when packing + tooltip - tooltip text + +The `select_mode` option can be a string or a constant value defined as a variable. Generally speaking strings are used for these kinds of options. + +ListBoxes can cause a window to return from a Read call. If the flag change_submits is set, then when a user makes a selection, the Read immediately returns. +Another way ListBoxes can cause Reads to return is if the flag bind_return_key is set. If True, then if the user presses the return key while an entry is selected, then the Read returns. Also, if this flag is set, if the user double-clicks an entry it will return from the Read. + ### Listbox Methods ```python Update(values=None, disabled=None) @@ -1708,53 +1760,53 @@ SetValue - Sets selection to one or more values GetListValues - Return the list of values to choose from -## Slider Element - -Sliders have a couple of slider-specific settings as well as appearance settings. Examples include the `orientation` and `range` settings. - - layout = [[sg.Slider(range=(1,500), default_value=222, size=(20,15), orientation='horizontal', font=('Helvetica', 12))]] - -![slider](https://user-images.githubusercontent.com/13696193/44959858-b4349200-aec3-11e8-9e25-c0fcf025d19e.jpg) - - Slider(range=(None,None), - default_value=None, - resolution=None, - orientation=None, - border_width=None, - relief=None, - change_submits=False, - disabled=False, - size=(None, None), - font=None, - background_color=None, - text_color=None, - key=None, - pad=None, - tooltip=None) +## Slider Element -. - - range - (min, max) slider's range - default_value - default setting (within range) +Sliders have a couple of slider-specific settings as well as appearance settings. Examples include the `orientation` and `range` settings. + + layout = [[sg.Slider(range=(1,500), default_value=222, size=(20,15), orientation='horizontal', font=('Helvetica', 12))]] + +![slider](https://user-images.githubusercontent.com/13696193/44959858-b4349200-aec3-11e8-9e25-c0fcf025d19e.jpg) + + Slider(range=(None,None), + default_value=None, + resolution=None, + orientation=None, + border_width=None, + relief=None, + change_submits=False, + disabled=False, + size=(None, None), + font=None, + background_color=None, + text_color=None, + key=None, + pad=None, + tooltip=None) + +. + + range - (min, max) slider's range + default_value - default setting (within range) resolution - how much each 'tick' should represent. Default = 1 - orientation - 'horizontal' or 'vertical' ('h' or 'v' work) - border_width - how deep the widget looks - relief - relief style. Values are same as progress meter relief values. Can be a constant or a string: - RELIEF_RAISED= 'raised' - RELIEF_SUNKEN= 'sunken' - RELIEF_FLAT= 'flat' - RELIEF_RIDGE= 'ridge' - RELIEF_GROOVE= 'groove' - RELIEF_SOLID = 'solid' - size - (width, height) of element in characters + orientation - 'horizontal' or 'vertical' ('h' or 'v' work) + border_width - how deep the widget looks + relief - relief style. Values are same as progress meter relief values. Can be a constant or a string: + RELIEF_RAISED= 'raised' + RELIEF_SUNKEN= 'sunken' + RELIEF_FLAT= 'flat' + RELIEF_RIDGE= 'ridge' + RELIEF_GROOVE= 'groove' + RELIEF_SOLID = 'solid' + size - (width, height) of element in characters disabled - Bool If True slider is disabled - auto_size_text - Bool. True if size should fit the text - background_color - color to use for the input field background - text_color - color to use for the typed text - change_submits - causes window read to immediately return if the checkbox value changes - key- Dictionary key to use for return values + auto_size_text - Bool. True if size should fit the text + background_color - color to use for the input field background + text_color - color to use for the typed text + change_submits - causes window read to immediately return if the checkbox value changes + key- Dictionary key to use for return values tooltip - Tooltip to display when hovered over wlement - + ### Slider Methods ```python Update(self, value=None, range=(None, None), disabled=None): @@ -1764,42 +1816,42 @@ range - change range of valid values disabled - if True disables element -## Radio Button Element - -Creates one radio button that is assigned to a group of radio buttons. Only 1 of the buttons in the group can be selected at any one time. - - layout = [[sg.Radio('My first Radio!', "RADIO1", default=True), sg.Radio('My second radio!', "RADIO1")]] - -![radio](https://user-images.githubusercontent.com/13696193/44959857-b4349200-aec3-11e8-8e2d-e6a49ffbd0b6.jpg) - - Radio(text, - group_id, - default=False, - size=(None, None), +## Radio Button Element + +Creates one radio button that is assigned to a group of radio buttons. Only 1 of the buttons in the group can be selected at any one time. + + layout = [[sg.Radio('My first Radio!', "RADIO1", default=True), sg.Radio('My second radio!', "RADIO1")]] + +![radio](https://user-images.githubusercontent.com/13696193/44959857-b4349200-aec3-11e8-8e2d-e6a49ffbd0b6.jpg) + + Radio(text, + group_id, + default=False, + size=(None, None), disabled = False, - auto_size_text=None, - font=None, - background_color = None, - text_color = None, + auto_size_text=None, + font=None, + background_color = None, + text_color = None, key = None, pad = None, - tooltip = None) - -. - - text - Text to display next to button - group_id - Groups together multiple Radio Buttons. Can be any value - default - Bool. Initial state - size - (width, height) size of element in characters - auto_size_text - Bool. True if should size width to fit text - font - Font type and size for text display - background_color - color to use for the background - text_color - color to use for the text - key - Dictionary key to use for return values + tooltip = None) + +. + + text - Text to display next to button + group_id - Groups together multiple Radio Buttons. Can be any value + default - Bool. Initial state + size - (width, height) size of element in characters + auto_size_text - Bool. True if should size width to fit text + font - Font type and size for text display + background_color - color to use for the background + text_color - color to use for the text + key - Dictionary key to use for return values pad - padding around element tooltip - tooltip to show when mouse hovered over element - - + + ### Radio Button Methods ```python Update(value=None, disabled=None) @@ -1808,39 +1860,39 @@ value - bool - if True change to selected disabled - if True disables the element -## Checkbox Element -Checkbox elements are like Radio Button elements. They return a bool indicating whether or not they are checked. - - layout = [[sg.Checkbox('My first Checkbox!', default=True), sg.Checkbox('My second Checkbox!')]] - - -![checkbox](https://user-images.githubusercontent.com/13696193/44959906-6f5d2b00-aec4-11e8-9c8a-962c787f0286.jpg) - - - Checkbox(text, - default=False, - size=(None, None), - auto_size_text=None, - font=None, - background_color = None, - text_color = None, - change_submits = False +## Checkbox Element +Checkbox elements are like Radio Button elements. They return a bool indicating whether or not they are checked. + + layout = [[sg.Checkbox('My first Checkbox!', default=True), sg.Checkbox('My second Checkbox!')]] + + +![checkbox](https://user-images.githubusercontent.com/13696193/44959906-6f5d2b00-aec4-11e8-9c8a-962c787f0286.jpg) + + + Checkbox(text, + default=False, + size=(None, None), + auto_size_text=None, + font=None, + background_color = None, + text_color = None, + change_submits = False disabled = False, key = None, pad = None, - tooltip = None): -. - - text - Text to display next to checkbox - default- Bool + None. Initial state. True = Checked, False = unchecked, None = Not available (grayed out) - size - (width, height) size of element in characters - auto_size_text- Bool. True if should size width to fit text + tooltip = None): +. + + text - Text to display next to checkbox + default- Bool + None. Initial state. True = Checked, False = unchecked, None = Not available (grayed out) + size - (width, height) size of element in characters + auto_size_text- Bool. True if should size width to fit text disabled - Bool. If True element is disabled - font- Font type and size for text display - background_color - color to use for the background - text_color - color to use for the typed text - change_submits - causes window read to immediately return if the checkbox value changes - key = Dictionary key to use for return values + font- Font type and size for text display + background_color - color to use for the background + text_color - color to use for the typed text + change_submits - causes window read to immediately return if the checkbox value changes + key = Dictionary key to use for return values pad - Padding around element in window tooltip - text to show when mouse is hovered over element @@ -1857,45 +1909,45 @@ disabled - if True disables the element Get - returns current state - -## Spin Element - -An up/down spinner control. The valid values are passed in as a list. - - layout = [[sg.Spin([i for i in range(1,11)], initial_value=1), sg.Text('Volume level')]] - -![spinner](https://user-images.githubusercontent.com/13696193/44959855-b1d23800-aec3-11e8-9f51-afb2109879da.jpg) - - Spin(values, - intiial_value=None, - disabled = False, - size=(None, None), + +## Spin Element + +An up/down spinner control. The valid values are passed in as a list. + + layout = [[sg.Spin([i for i in range(1,11)], initial_value=1), sg.Text('Volume level')]] + +![spinner](https://user-images.githubusercontent.com/13696193/44959855-b1d23800-aec3-11e8-9f51-afb2109879da.jpg) + + Spin(values, + intiial_value=None, + disabled = False, + size=(None, None), change_submits = False, - auto_size_text=None, - font=None, - background_color = None, - text_color = None, + auto_size_text=None, + font=None, + background_color = None, + text_color = None, key = None. pad = None, - tooltip = None): + tooltip = None): Parameter definitions - - values - List of valid values - initial_value - String with initial value - size - (width, height) size of element in characters - auto_size_text - Bool. True if should size width to fit text - font - Font type and size for text display + + values - List of valid values + initial_value - String with initial value + size - (width, height) size of element in characters + auto_size_text - Bool. True if should size width to fit text + font - Font type and size for text display disabled - Bool. If True element is disabled - background_color - color to use for the background - text_color - color to use for the typed text - change_submits - causes window read to immediately return if the spinner value changes - key = Dictionary key to use for return values + background_color - color to use for the background + text_color - color to use for the typed text + change_submits - causes window read to immediately return if the spinner value changes + key = Dictionary key to use for return values pad - padding around element in the window - tooltip - text to show when mouse hovered over element - + tooltip - text to show when mouse hovered over element + ### Spin Methods ```python Update(value=None, values=None, disabled=None) @@ -1908,16 +1960,16 @@ disabled - if True disables the element Images can be placed in your window provide they are in PNG, GIF, PPM/PGM format. JPGs cannot be shown because tkinter does not naively support JPGs. You can use the Python Imaging Library (PIL) package to convert your image to PNG prior to calling PySimpleGUI if your images are in JPG format. - Image(filename=None, - data=None, - background_color=None, - size=(None, None), - pad=None, + Image(filename=None, + data=None, + background_color=None, + size=(None, None), + pad=None, key=None, tooltip=None) - + Parameter definitions - + filename - file name if the image is in a file data - if image is in RAM (PIL format?) background_color - Color of background @@ -1936,279 +1988,279 @@ Like other Elements, the Image Element has an update method. Call Update if you Choose **either** a filename or in-ram data image to use to replace current image -## Button Element - -Buttons are the most important element of all! They cause the majority of the action to happen. After all, it's a button press that will get you out of a window, whether it be Submit or Cancel, one way or another a button is involved in all windows. The only exception is to this is when the user closes the window using the "X" in the upper corner which means no button was involved. - -The Types of buttons include: -* Folder Browse -* File Browse -* Files Browse -* File SaveAs -* File Save -* Close window (normal button) -* Read window -* Realtime -* Calendar Chooser -* Color Chooser - - - Close window - Normal buttons like Submit, Cancel, Yes, No, etc, are "Close window" buttons. They cause the input values to be read and then the window is ***closed***, returning the values to the caller. - -Folder Browse - When clicked a folder browse dialog box is opened. The results of the Folder Browse dialog box are written into one of the input fields of the window. - -File Browse - Same as the Folder Browse except rather than choosing a folder, a single file is chosen. - -Calendar Chooser - Opens a graphical calendar to select a date. - -Color Chooser - Opens a color chooser dialog - -Read window - This is a window button that will read a snapshot of all of the input fields, but does not close the window after it's clicked. - -Realtime - This is another async window button. Normal button clicks occur after a button's click is released. Realtime buttons report a click the entire time the button is held down. - -Most programs will use a combination of shortcut button calls (Submit, Cancel, etc), plain buttons that close the window, and ReadForm buttons that keep the window open but returns control back to the caller. - -Sometimes there are multiple names for the same function. This is simply to make the job of the programmer quicker and easier. - -The 3 primary windows of PySimpleGUI buttons and their names are: - - 1. `Button` = `SimpleButton` - 2. `ReadButton` = `RButton` = `ReadFormButton` (old style... use ReadButton instead) - 3. `RealtimeButton` - -You will find the long-form in the older programs. - -The most basic Button element call to use is `Button` - - Button(button_text='' - button_type=BUTTON_TYPE_CLOSES_WIN - target=(None, None) - tooltip=None - file_types=(("ALL Files", "*.*"),) - initial_folder=None +## Button Element + +Buttons are the most important element of all! They cause the majority of the action to happen. After all, it's a button press that will get you out of a window, whether it be Submit or Cancel, one way or another a button is involved in all windows. The only exception is to this is when the user closes the window using the "X" in the upper corner which means no button was involved. + +The Types of buttons include: +* Folder Browse +* File Browse +* Files Browse +* File SaveAs +* File Save +* Close window (normal button) +* Read window +* Realtime +* Calendar Chooser +* Color Chooser + + + Close window - Normal buttons like Submit, Cancel, Yes, No, etc, are "Close window" buttons. They cause the input values to be read and then the window is ***closed***, returning the values to the caller. + +Folder Browse - When clicked a folder browse dialog box is opened. The results of the Folder Browse dialog box are written into one of the input fields of the window. + +File Browse - Same as the Folder Browse except rather than choosing a folder, a single file is chosen. + +Calendar Chooser - Opens a graphical calendar to select a date. + +Color Chooser - Opens a color chooser dialog + +Read window - This is a window button that will read a snapshot of all of the input fields, but does not close the window after it's clicked. + +Realtime - This is another async window button. Normal button clicks occur after a button's click is released. Realtime buttons report a click the entire time the button is held down. + +Most programs will use a combination of shortcut button calls (Submit, Cancel, etc), plain buttons that close the window, and ReadForm buttons that keep the window open but returns control back to the caller. + +Sometimes there are multiple names for the same function. This is simply to make the job of the programmer quicker and easier. + +The 3 primary windows of PySimpleGUI buttons and their names are: + + 1. `Button` = `SimpleButton` + 2. `ReadButton` = `RButton` = `ReadFormButton` (old style... use ReadButton instead) + 3. `RealtimeButton` + +You will find the long-form in the older programs. + +The most basic Button element call to use is `Button` + + Button(button_text='' + button_type=BUTTON_TYPE_CLOSES_WIN + target=(None, None) + tooltip=None + file_types=(("ALL Files", "*.*"),) + initial_folder=None disabled = False - image_filename=None + image_filename=None image_data=None - image_size=(None, None) - image_subsample=None - border_width=None - size=(None, None) - auto_size_button=None - button_color=None - default_value = None - font=None - bind_return_key=False - focus=False - pad=None - key=None): - -Parameters - - button_text - Text to be displayed on the button - button_type - You should NOT be setting this directly - target - key or (row,col) target for the button - tooltip - tooltip text for the button - file_types - the filetypes that will be used to match files - initial_folder - starting path for folders and files + image_size=(None, None) + image_subsample=None + border_width=None + size=(None, None) + auto_size_button=None + button_color=None + default_value = None + font=None + bind_return_key=False + focus=False + pad=None + key=None): + +Parameters + + button_text - Text to be displayed on the button + button_type - You should NOT be setting this directly + target - key or (row,col) target for the button + tooltip - tooltip text for the button + file_types - the filetypes that will be used to match files + initial_folder - starting path for folders and files disabled = Bool If True button is disabled - image_filename - image filename if there is a button image + image_filename - image filename if there is a button image image_data - in-RAM image to be displayed on button - image_size - size of button image in pixels - image_subsample - amount to reduce the size of the image - border_width - width of border around button in pixels - size - size in characters - auto_size_button - True if button size is determined by button text - button_color - (text color, backound color) - default_value - initial value for buttons that hold information - font - font to use for button text - bind_return_key - If True the return key will cause this button to fire - focus - if focus should be set to this button - pad - (x,y) padding in pixels for packing the button - key - key used for finding the element - -#### Pre-defined Buttons -These Pre-made buttons are some of the most important elements of all because they are used so much. They all basically do the same thing, set the button text to match the function name and set the parameters to commonly used values. If you find yourself needing to create a custom button often because it's not on this list, please post a request on GitHub. . They include: - - OK - Ok - Submit - Cancel - Yes - No - Exit - Quit - Help - Save - SaveAs - FileBrowse - FilesBrowse - FileSaveAs - FolderBrowse -. - layout = [[sg.OK(), sg.Cancel()]] - -![ok cancel 3](https://user-images.githubusercontent.com/13696193/44959927-aa5f5e80-aec4-11e8-86e1-5dc0b3a2b803.jpg) - - #### Button targets - -The `FileBrowse`, `FolderBrowse`, `FileSaveAs` , `FilesSaveAs`, `CalendarButton`, `ColorChooserButton` buttons all fill-in values into another element located on the window. The target can be a Text Element or an InputText Element. The location of the element is specified by the `target` variable in the function call. - -The Target comes in two forms. -1. Key -2. (row, column) - -Targets that are specified using a key will find its target element by using the target's key value. This is the "preferred" method. - -If the Target is specified using (row, column) then it utilizes a grid system. The rows in your GUI are numbered starting with 0. The target can be specified as a hard coded grid item or it can be relative to the button. - -The (row, col) targeting can only target elements that are in the same "container". Containers are the Window, Column and Frame Elements. A File Browse button located inside of a Column is unable to target elements outside of that Column. - -The default value for `target` is `(ThisRow, -1)`. `ThisRow` is a special value that tells the GUI to use the same row as the button. The Y-value of -1 means the field one value to the left of the button. For a File or Folder Browse button, the field that it fills are generally to the left of the button is most cases. (ThisRow, -1) means the Element to the left of the button, on the same row. - -If a value of `(None, None)` is chosen for the target, then the button itself will hold the information. Later the button can be queried for the value by using the button's key. - -Let's examine this window as an example: - - -![file browse](https://user-images.githubusercontent.com/13696193/44959944-d1b62b80-aec4-11e8-8a68-9d79d37b2c81.jpg) - - -The `InputText` element is located at (1,0)... row 1, column 0. The `Browse` button is located at position (2,0). The Target for the button could be any of these values: - - Target = (1,0) - Target = (-1,0) - -The code for the entire window could be: - - layout = [[sg.T('Source Folder')], - [sg.In()], - [sg.FolderBrowse(target=(-1, 0)), sg.OK()]] - -or if using keys, then the code would be: - - layout = [[sg.T('Source Folder')], - [sg.In(key='input')], - [sg.FolderBrowse(target='input'), sg.OK()]] - -See how much easier the key method is? - -**Save & Open Buttons** - -There are 3 different types of File/Folder open dialog box available. If you are looking for a file to open, the `FileBrowse` is what you want. If you want to save a file, `SaveAs` is the button. If you want to get a folder name, then `FolderBrowse` is the button to use. To open several files at once, use the `FilesBrowse` button. It will create a list of files that are separated by ';' - - -![open](https://user-images.githubusercontent.com/13696193/45243804-2b529780-b2c3-11e8-90dc-6c9061db2a1e.jpg) - - -![folder](https://user-images.githubusercontent.com/13696193/45243805-2b529780-b2c3-11e8-95ee-fec3c0b11319.jpg) - - -![saveas](https://user-images.githubusercontent.com/13696193/45243807-2beb2e00-b2c3-11e8-8549-ba71cdc05951.jpg) - - - -**Calendar Buttons** - -These buttons pop up a calendar chooser window. The chosen date is returned as a string. - -![calendar](https://user-images.githubusercontent.com/13696193/45243374-99965a80-b2c1-11e8-8311-49777835ca40.jpg) - -**Color Chooser Buttons** - -These buttons pop up a standard color chooser window. The result is returned as a tuple. One of the returned values is an RGB hex representation. - -![color](https://user-images.githubusercontent.com/13696193/45243375-99965a80-b2c1-11e8-9779-b71bed85fab6.jpg) - - -**Custom Buttons** -Not all buttons are created equal. A button that closes a window is different that a button that returns from the window without closing it. If you want to define your own button, you will generally do this with the Button Element `Button`, which closes the window when clicked. - -layout = [[sg.Button('My Button')]] - -![button](https://user-images.githubusercontent.com/13696193/44959862-b696ec00-aec3-11e8-9e88-4b9af0338a03.jpg) - -All buttons can have their text changed by changing the `button_text` variable in the button call. It is this text that is returned when a window is read. This text will be what tells you which button is called so make it unique. Most of the convenience buttons (Submit, Cancel, Yes, etc) are all Buttons. Some that are not are `FileBrowse` , `FolderBrowse`, `FileSaveAs`. They clearly do not close the window. Instead they bring up a file or folder browser dialog box. - -**Button Images** -Now this is an exciting feature not found in many simplified packages.... images on buttons! You can make a pretty spiffy user interface with the help of a few button images. - -Your button images need to be in PNG or GIF format. When you make a button with an image, set the button background to the same color as the background. There's a button color TRANSPARENT_BUTTON that you can set your button color to in order for it to blend into the background. Note that this value is currently the same as the color as the default system background on Windows. - -This example comes from the `Demo Media Player.py` example program. Because it's a non-blocking button, it's defined as `RButton`. You also put images on blocking buttons by using `Button`. - - - sg.RButton('Restart Song', button_color=sg.TRANSPARENT_BUTTON, - image_filename=image_restart, image_size=(50, 50), image_subsample=2, border_width=0) - -Three parameters are used for button images. - - image_filename - Filename. Can be a relative path - image_size - Size of image file in pixels - image_subsample - Amount to divide the size by. 2 means your image will be 1/2 the size. 3 means 1/3 - -Here's an example window made with button images. - -![media file player](https://user-images.githubusercontent.com/13696193/43161977-9ee7cace-8f57-11e8-8ff8-3ea24b69dab9.jpg) - -You'll find the source code in the file Demo Media Player. Here is what the button calls look like to create media player window - - sg.RButton('Pause', button_color=sg.TRANSPARENT_BUTTON, - image_filename=image_pause, image_size=(50, 50), image_subsample=2, border_width=0) - -This is one you'll have to experiment with at this point. Not up for an exhaustive explanation. - - **Realtime Buttons** - - Normally buttons are considered "clicked" when the mouse button is let UP after a downward click on the button. What about times when you need to read the raw up/down button values. A classic example for this is a robotic remote control. Building a remote control using a GUI is easy enough. One button for each of the directions is a start. Perhaps something like this: - -![robot remote](https://user-images.githubusercontent.com/13696193/44959958-ff9b7000-aec4-11e8-99ea-7450926409be.jpg) - - -This window has 2 button types. There's the normal "Simple Button" (Quit) and 4 "Realtime Buttons". - -Here is the code to make, show and get results from this window: - - window = sg.Window('Robotics Remote Control', auto_size_text=True) - - window_rows = [[sg.Text('Robotics Remote Control')], - [sg.T(' '*10), sg.RealtimeButton('Forward')], - [ sg.RealtimeButton('Left'), sg.T(' '*15), sg.RealtimeButton('Right')], - [sg.T(' '*10), sg.RealtimeButton('Reverse')], - [sg.T('')], - [sg.Quit(button_color=('black', 'orange'))] - ] - - window.Layout(window_rows, non_blocking=True).Read() - -Somewhere later in your code will be your main event loop. This is where you do your polling of devices, do input/output, etc. It's here that you will read your window's buttons. - - while (True): - # This is the code that reads and updates your window - button, values = window.ReadNonBlocking() - if button is not None: - sg.Print(button) - if button == 'Quit' or values is None: - break - time.sleep(.01) - -This loop will read button values and print them. When one of the Realtime buttons is clicked, the call to `window.ReadNonBlocking` will return a button name matching the name on the button that was depressed. It will continue to return values as long as the button remains depressed. Once released, the ReadNonBlocking will return None for buttons until a button is again clicked. - -**File Types** -The `FileBrowse` & `SaveAs` buttons have an additional setting named `file_types`. This variable is used to filter the files shown in the file dialog box. The default value for this setting is - - FileTypes=(("ALL Files", "*.*"),) - -This code produces a window where the Browse button only shows files of type .TXT - - layout = [[sg.In() ,sg.FileBrowse(file_types=(("Text Files", "*.txt"),))]] - - ***The ENTER key*** - The ENTER key is an important part of data entry for windows. There's a long tradition of the enter key being used to quickly submit windows. PySimpleGUI implements this by tying the ENTER key to the first button that closes or reads a window. - -The Enter Key can be "bound" to a particular button so that when the key is pressed, it causes the window to return as if the button was clicked. This is done using the `bind_return_key` parameter in the button calls. -If there are more than 1 button on a window, the FIRST button that is of type Close window or Read window is used. First is determined by scanning the window, top to bottom and left to right. - + image_size - size of button image in pixels + image_subsample - amount to reduce the size of the image + border_width - width of border around button in pixels + size - size in characters + auto_size_button - True if button size is determined by button text + button_color - (text color, backound color) + default_value - initial value for buttons that hold information + font - font to use for button text + bind_return_key - If True the return key will cause this button to fire + focus - if focus should be set to this button + pad - (x,y) padding in pixels for packing the button + key - key used for finding the element + +#### Pre-defined Buttons +These Pre-made buttons are some of the most important elements of all because they are used so much. They all basically do the same thing, set the button text to match the function name and set the parameters to commonly used values. If you find yourself needing to create a custom button often because it's not on this list, please post a request on GitHub. . They include: + + OK + Ok + Submit + Cancel + Yes + No + Exit + Quit + Help + Save + SaveAs + FileBrowse + FilesBrowse + FileSaveAs + FolderBrowse +. + layout = [[sg.OK(), sg.Cancel()]] + +![ok cancel 3](https://user-images.githubusercontent.com/13696193/44959927-aa5f5e80-aec4-11e8-86e1-5dc0b3a2b803.jpg) + + #### Button targets + +The `FileBrowse`, `FolderBrowse`, `FileSaveAs` , `FilesSaveAs`, `CalendarButton`, `ColorChooserButton` buttons all fill-in values into another element located on the window. The target can be a Text Element or an InputText Element. The location of the element is specified by the `target` variable in the function call. + +The Target comes in two forms. +1. Key +2. (row, column) + +Targets that are specified using a key will find its target element by using the target's key value. This is the "preferred" method. + +If the Target is specified using (row, column) then it utilizes a grid system. The rows in your GUI are numbered starting with 0. The target can be specified as a hard coded grid item or it can be relative to the button. + +The (row, col) targeting can only target elements that are in the same "container". Containers are the Window, Column and Frame Elements. A File Browse button located inside of a Column is unable to target elements outside of that Column. + +The default value for `target` is `(ThisRow, -1)`. `ThisRow` is a special value that tells the GUI to use the same row as the button. The Y-value of -1 means the field one value to the left of the button. For a File or Folder Browse button, the field that it fills are generally to the left of the button is most cases. (ThisRow, -1) means the Element to the left of the button, on the same row. + +If a value of `(None, None)` is chosen for the target, then the button itself will hold the information. Later the button can be queried for the value by using the button's key. + +Let's examine this window as an example: + + +![file browse](https://user-images.githubusercontent.com/13696193/44959944-d1b62b80-aec4-11e8-8a68-9d79d37b2c81.jpg) + + +The `InputText` element is located at (1,0)... row 1, column 0. The `Browse` button is located at position (2,0). The Target for the button could be any of these values: + + Target = (1,0) + Target = (-1,0) + +The code for the entire window could be: + + layout = [[sg.T('Source Folder')], + [sg.In()], + [sg.FolderBrowse(target=(-1, 0)), sg.OK()]] + +or if using keys, then the code would be: + + layout = [[sg.T('Source Folder')], + [sg.In(key='input')], + [sg.FolderBrowse(target='input'), sg.OK()]] + +See how much easier the key method is? + +**Save & Open Buttons** + +There are 3 different types of File/Folder open dialog box available. If you are looking for a file to open, the `FileBrowse` is what you want. If you want to save a file, `SaveAs` is the button. If you want to get a folder name, then `FolderBrowse` is the button to use. To open several files at once, use the `FilesBrowse` button. It will create a list of files that are separated by ';' + + +![open](https://user-images.githubusercontent.com/13696193/45243804-2b529780-b2c3-11e8-90dc-6c9061db2a1e.jpg) + + +![folder](https://user-images.githubusercontent.com/13696193/45243805-2b529780-b2c3-11e8-95ee-fec3c0b11319.jpg) + + +![saveas](https://user-images.githubusercontent.com/13696193/45243807-2beb2e00-b2c3-11e8-8549-ba71cdc05951.jpg) + + + +**Calendar Buttons** + +These buttons pop up a calendar chooser window. The chosen date is returned as a string. + +![calendar](https://user-images.githubusercontent.com/13696193/45243374-99965a80-b2c1-11e8-8311-49777835ca40.jpg) + +**Color Chooser Buttons** + +These buttons pop up a standard color chooser window. The result is returned as a tuple. One of the returned values is an RGB hex representation. + +![color](https://user-images.githubusercontent.com/13696193/45243375-99965a80-b2c1-11e8-9779-b71bed85fab6.jpg) + + +**Custom Buttons** +Not all buttons are created equal. A button that closes a window is different that a button that returns from the window without closing it. If you want to define your own button, you will generally do this with the Button Element `Button`, which closes the window when clicked. + +layout = [[sg.Button('My Button')]] + +![button](https://user-images.githubusercontent.com/13696193/44959862-b696ec00-aec3-11e8-9e88-4b9af0338a03.jpg) + +All buttons can have their text changed by changing the `button_text` variable in the button call. It is this text that is returned when a window is read. This text will be what tells you which button is called so make it unique. Most of the convenience buttons (Submit, Cancel, Yes, etc) are all Buttons. Some that are not are `FileBrowse` , `FolderBrowse`, `FileSaveAs`. They clearly do not close the window. Instead they bring up a file or folder browser dialog box. + +**Button Images** +Now this is an exciting feature not found in many simplified packages.... images on buttons! You can make a pretty spiffy user interface with the help of a few button images. + +Your button images need to be in PNG or GIF format. When you make a button with an image, set the button background to the same color as the background. There's a button color TRANSPARENT_BUTTON that you can set your button color to in order for it to blend into the background. Note that this value is currently the same as the color as the default system background on Windows. + +This example comes from the `Demo Media Player.py` example program. Because it's a non-blocking button, it's defined as `RButton`. You also put images on blocking buttons by using `Button`. + + + sg.RButton('Restart Song', button_color=sg.TRANSPARENT_BUTTON, + image_filename=image_restart, image_size=(50, 50), image_subsample=2, border_width=0) + +Three parameters are used for button images. + + image_filename - Filename. Can be a relative path + image_size - Size of image file in pixels + image_subsample - Amount to divide the size by. 2 means your image will be 1/2 the size. 3 means 1/3 + +Here's an example window made with button images. + +![media file player](https://user-images.githubusercontent.com/13696193/43161977-9ee7cace-8f57-11e8-8ff8-3ea24b69dab9.jpg) + +You'll find the source code in the file Demo Media Player. Here is what the button calls look like to create media player window + + sg.RButton('Pause', button_color=sg.TRANSPARENT_BUTTON, + image_filename=image_pause, image_size=(50, 50), image_subsample=2, border_width=0) + +This is one you'll have to experiment with at this point. Not up for an exhaustive explanation. + + **Realtime Buttons** + + Normally buttons are considered "clicked" when the mouse button is let UP after a downward click on the button. What about times when you need to read the raw up/down button values. A classic example for this is a robotic remote control. Building a remote control using a GUI is easy enough. One button for each of the directions is a start. Perhaps something like this: + +![robot remote](https://user-images.githubusercontent.com/13696193/44959958-ff9b7000-aec4-11e8-99ea-7450926409be.jpg) + + +This window has 2 button types. There's the normal "Simple Button" (Quit) and 4 "Realtime Buttons". + +Here is the code to make, show and get results from this window: + + window = sg.Window('Robotics Remote Control', auto_size_text=True) + + window_rows = [[sg.Text('Robotics Remote Control')], + [sg.T(' '*10), sg.RealtimeButton('Forward')], + [ sg.RealtimeButton('Left'), sg.T(' '*15), sg.RealtimeButton('Right')], + [sg.T(' '*10), sg.RealtimeButton('Reverse')], + [sg.T('')], + [sg.Quit(button_color=('black', 'orange'))] + ] + + window.Layout(window_rows, non_blocking=True).Read() + +Somewhere later in your code will be your main event loop. This is where you do your polling of devices, do input/output, etc. It's here that you will read your window's buttons. + + while (True): + # This is the code that reads and updates your window + event, values = window.ReadNonBlocking() + if button is not None: + sg.Print(event) + if event == 'Quit' or values is None: + break + time.sleep(.01) + +This loop will read button values and print them. When one of the Realtime buttons is clicked, the call to `window.ReadNonBlocking` will return a button name matching the name on the button that was depressed. It will continue to return values as long as the button remains depressed. Once released, the ReadNonBlocking will return None for buttons until a button is again clicked. + +**File Types** +The `FileBrowse` & `SaveAs` buttons have an additional setting named `file_types`. This variable is used to filter the files shown in the file dialog box. The default value for this setting is + + FileTypes=(("ALL Files", "*.*"),) + +This code produces a window where the Browse button only shows files of type .TXT + + layout = [[sg.In() ,sg.FileBrowse(file_types=(("Text Files", "*.txt"),))]] + + ***The ENTER key*** + The ENTER key is an important part of data entry for windows. There's a long tradition of the enter key being used to quickly submit windows. PySimpleGUI implements this by tying the ENTER key to the first button that closes or reads a window. + +The Enter Key can be "bound" to a particular button so that when the key is pressed, it causes the window to return as if the button was clicked. This is done using the `bind_return_key` parameter in the button calls. +If there are more than 1 button on a window, the FIRST button that is of type Close window or Read window is used. First is determined by scanning the window, top to bottom and left to right. + ### Button Methods ```python @@ -2223,75 +2275,75 @@ disabled - if True disables the button image_data - sets button image to in-ram image image_filename - sets button image using a file - --- + --- ## ProgressBar Element -The `ProgressBar` element is used to build custom Progress Bar windows. It is HIGHLY recommended that you use OneLineProgressMeter that provides a complete progress meter solution for you. Progress Meters are not easy to work with because the windows have to be non-blocking and they are tricky to debug. - -The **easiest** way to get progress meters into your code is to use the `OneLineProgressMeter` API. This consists of a pair of functions, `OneLineProgressMeter` and `OneLineProgressMeterCancel`. You can easily cancel any progress meter by calling it with the current value = max value. This will mark the meter as expired and close the window. -You've already seen OneLineProgressMeter calls presented earlier in this readme. - - sg.OneLineProgressMeter('My Meter', i+1, 1000, 'key', 'Optional message') - -The return value for `OneLineProgressMeter` is: -`True` if meter updated correctly -`False` if user clicked the Cancel button, closed the window, or vale reached the max value. - -#### Progress Mater in Your window -Another way of using a Progress Meter with PySimpleGUI is to build a custom window with a `ProgressBar` Element in the window. You will need to run your window as a non-blocking window. When you are ready to update your progress bar, you call the `UpdateBar` method for the `ProgressBar` element itself. - -![progress custom](https://user-images.githubusercontent.com/13696193/45243969-c3508100-b2c3-11e8-82bc-927d0307e093.jpg) - - import PySimpleGUI as sg - - # layout the window - layout = [[sg.Text('A custom progress meter')], - [sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progressbar')], - [sg.Cancel()]] - - # create the window` - window = sg.Window('Custom Progress Meter').Layout(layout) - progress_bar = window.FindElement('progressbar') - # loop that would normally do something useful - for i in range(10000): - # check to see if the cancel button was clicked and exit loop if clicked - button, values = window.ReadNonBlocking() - if button == 'Cancel' or values == None: - break - # update bar with loop value +1 so that bar eventually reaches the maximum - progress_bar.UpdateBar(i + 1) - # done with loop... need to destroy the window as it's still open - window.CloseNonBlocking()) - - -#### Output -The Output Element is a re-direction of Stdout. Anything "printed" will be displayed in this element. - - Output(size=(None, None)) - -Here's a complete solution for a chat-window using an Async window with an Output Element - - import PySimpleGUI as sg - - # Blocking window that doesn't close - def ChatBot(): - layout = [[(sg.Text('This is where standard out is being routed', size=[40, 1]))], - [sg.Output(size=(80, 20))], - [sg.Multiline(size=(70, 5), enter_submits=True), - sg.RButton('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0])), - sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]] - - window = sg.Window('Chat Window', default_element_size=(30, 2)).Layout(layout) - - # ---===--- Loop taking in user input and using it to query HowDoI web oracle --- # - while True: - button, value = window.Read() - if button == 'SEND': - print(value) - else: - break - - ChatBot() - +The `ProgressBar` element is used to build custom Progress Bar windows. It is HIGHLY recommended that you use OneLineProgressMeter that provides a complete progress meter solution for you. Progress Meters are not easy to work with because the windows have to be non-blocking and they are tricky to debug. + +The **easiest** way to get progress meters into your code is to use the `OneLineProgressMeter` API. This consists of a pair of functions, `OneLineProgressMeter` and `OneLineProgressMeterCancel`. You can easily cancel any progress meter by calling it with the current value = max value. This will mark the meter as expired and close the window. +You've already seen OneLineProgressMeter calls presented earlier in this readme. + + sg.OneLineProgressMeter('My Meter', i+1, 1000, 'key', 'Optional message') + +The return value for `OneLineProgressMeter` is: +`True` if meter updated correctly +`False` if user clicked the Cancel button, closed the window, or vale reached the max value. + +#### Progress Mater in Your window +Another way of using a Progress Meter with PySimpleGUI is to build a custom window with a `ProgressBar` Element in the window. You will need to run your window as a non-blocking window. When you are ready to update your progress bar, you call the `UpdateBar` method for the `ProgressBar` element itself. + +![progress custom](https://user-images.githubusercontent.com/13696193/45243969-c3508100-b2c3-11e8-82bc-927d0307e093.jpg) + + import PySimpleGUI as sg + + # layout the window + layout = [[sg.Text('A custom progress meter')], + [sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progressbar')], + [sg.Cancel()]] + + # create the window` + window = sg.Window('Custom Progress Meter').Layout(layout) + progress_bar = window.FindElement('progressbar') + # loop that would normally do something useful + for i in range(10000): + # check to see if the cancel button was clicked and exit loop if clicked + event, values = window.ReadNonBlocking() + if event == 'Cancel' or values == None: + break + # update bar with loop value +1 so that bar eventually reaches the maximum + progress_bar.UpdateBar(i + 1) + # done with loop... need to destroy the window as it's still open + window.CloseNonBlocking()) + + +#### Output +The Output Element is a re-direction of Stdout. Anything "printed" will be displayed in this element. + + Output(size=(None, None)) + +Here's a complete solution for a chat-window using an Async window with an Output Element + + import PySimpleGUI as sg + + # Blocking window that doesn't close + def ChatBot(): + layout = [[(sg.Text('This is where standard out is being routed', size=[40, 1]))], + [sg.Output(size=(80, 20))], + [sg.Multiline(size=(70, 5), enter_submits=True), + sg.RButton('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0])), + sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]] + + window = sg.Window('Chat Window', default_element_size=(30, 2)).Layout(layout) + + # ---===--- Loop taking in user input and using it to query HowDoI web oracle --- # + while True: + event, value = window.Read() + if event == 'SEND': + print(value) + else: + break + + ChatBot() + ### ProgressBar Methods ```python UpdateBar(current_count, max=None) @@ -2301,190 +2353,190 @@ max - changes the max value ## Column Element -Starting in version 2.9 you'll be able to do more complex layouts by using the Column Element. Think of a Column as a window within a window. And, yes, you can have a Column within a Column if you want. - -Columns are specified in exactly the same way as a window is, as a list of lists. - - def Column(layout - the list of rows that define the layout - background_color - color of background - size - size of visible portion of column - pad - element padding to use when packing - scrollable - bool. True if should add scrollbars - - -Columns are needed when you have an element that has a height > 1 line on the left, with single-line elements on the right. Here's an example of this kind of layout: - - -![column](https://user-images.githubusercontent.com/13696193/44959988-66b92480-aec5-11e8-9c26-316ed24a68c0.jpg) - - -This code produced the above window. - - - import PySimpleGUI as sg - - # Demo of how columns work - # window has on row 1 a vertical slider followed by a COLUMN with 7 rows - # Prior to the Column element, this layout was not possible - # Columns layouts look identical to window layouts, they are a list of lists of elements. - - window = sg.Window('Columns') # blank window - - # Column layout - col = [[sg.Text('col Row 1')], - [sg.Text('col Row 2'), sg.Input('col input 1')], - [sg.Text('col Row 3'), sg.Input('col input 2')], - [sg.Text('col Row 4'), sg.Input('col input 3')], - [sg.Text('col Row 5'), sg.Input('col input 4')], - [sg.Text('col Row 6'), sg.Input('col input 5')], - [sg.Text('col Row 7'), sg.Input('col input 6')]] - - layout = [[sg.Slider(range=(1,100), default_value=10, orientation='v', size=(8,20)), sg.Column(col)], - [sg.In('Last input')], - [sg.OK()]] - - # Display the window and get values - # If you're willing to not use the "context manager" design pattern, then it's possible - # to collapse the window display and read down to a single line of code. - button, values = sg.Window('Compact 1-line window with column').Layout(layout).Read() - - sg.Popup(button, values, line_width=200) - -The Column Element has 1 required parameter and 1 optional (the layout and the background color). Setting the background color has the same effect as setting the window's background color, except it only affects the column rectangle. - - Column(layout, background_color=None) - -The default background color for Columns is the same as the default window background color. If you change the look and feel of the window, the column background will match the window background automatically. - - - ----- -## Frame Element (Labelled Frames, Frames with a title) - -Frames work exactly the same way as Columns. You create layout that is then used to initialize the Frame. - - def Frame(title - the label / title to put on frame - layout - list of rows of elements the frame contains - title_color - color of the title text - background_color - color of background - title_location - locations to put the title - relief - type of relief to use - size - size of Frame in characters. Do not use if you want frame to autosize - font - font to use for title - pad - element padding to use when packing - border_width - how thick the line going around frame should be - key - key used to location the element - tooltip - tooltip text - - - -This code creates a window with a Frame and 2 buttons. - - frame_layout = [ - [sg.T('Text inside of a frame')], - [sg.CB('Check 1'), sg.CB('Check 2')], - ] - layout = [ - [sg.Frame('My Frame Title', frame_layout, font='Any 12', title_color='blue')], - [sg.Submit(), sg.Cancel()] - ] - - window = sg.Window('Frame with buttons', font=("Helvetica", 12)).Layout(layout) - - -![frame element](https://user-images.githubusercontent.com/13696193/45889173-c2245700-bd8d-11e8-8f73-1e5f1be3ddb1.jpg) - - - -Notice how the Frame layout looks identical to a window layout. A window works exactly the same way as a Column and a Frame. They all are "container elements". Elements that contain other elements. - -*These container Elements can be nested as deep as you want.* That's a pretty spiffy feature, right? Took a lot of work so be appreciative. Recursive code isn't trivial. - -## Canvas Element - -In my opinion, the tkinter Canvas Widget is the most powerful of the tkinter widget. While I try my best to completely isolate the user from anything that is tkinter related, the Canvas Element is the one exception. It enables integration with a number of other packages, often with spectacular results. - -### Matplotlib, Pyplot Integration - -One such integration is with Matploplib and Pyplot. There is a Demo program written that you can use as a design pattern to get an understanding of how to use the Canvas Widget once you get it. - - def Canvas(canvas - a tkinter canvasf if you created one. Normally not set - background_color - canvas color - size - size in pixels - pad - element padding for packing - key - key used to lookup element - tooltip - tooltip text - -The order of operations to obtain a tkinter Canvas Widget is: - - figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds - # define the window layout - layout = [[sg.Text('Plot test')], - [sg.Canvas(size=(figure_w, figure_h), key='canvas')], - [sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]] - - # create the window and show it without the plot - window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout).Finalize() - - - # add the plot to the window - fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig) - - # show it all again and get buttons - button, values = window.Read() - -To get a tkinter Canvas Widget from PySimpleGUI, follow these steps: -* Add Canvas Element to your window -* Layout your window -* Call `window.Finalize()` - this is a critical step you must not forget -* Find the Canvas Element by looking up using key -* Your Canvas Widget Object will be the found_element.TKCanvas -* Draw on your canvas to your heart's content -* Call `window.Read()` - Nothing will appear on your canvas until you call Read - -See `Demo_Matplotlib.py` for a Recipe you can copy. - - +Starting in version 2.9 you'll be able to do more complex layouts by using the Column Element. Think of a Column as a window within a window. And, yes, you can have a Column within a Column if you want. + +Columns are specified in exactly the same way as a window is, as a list of lists. + + def Column(layout - the list of rows that define the layout + background_color - color of background + size - size of visible portion of column + pad - element padding to use when packing + scrollable - bool. True if should add scrollbars + + +Columns are needed when you have an element that has a height > 1 line on the left, with single-line elements on the right. Here's an example of this kind of layout: + + +![column](https://user-images.githubusercontent.com/13696193/44959988-66b92480-aec5-11e8-9c26-316ed24a68c0.jpg) + + +This code produced the above window. + + + import PySimpleGUI as sg + + # Demo of how columns work + # window has on row 1 a vertical slider followed by a COLUMN with 7 rows + # Prior to the Column element, this layout was not possible + # Columns layouts look identical to window layouts, they are a list of lists of elements. + + window = sg.Window('Columns') # blank window + + # Column layout + col = [[sg.Text('col Row 1')], + [sg.Text('col Row 2'), sg.Input('col input 1')], + [sg.Text('col Row 3'), sg.Input('col input 2')], + [sg.Text('col Row 4'), sg.Input('col input 3')], + [sg.Text('col Row 5'), sg.Input('col input 4')], + [sg.Text('col Row 6'), sg.Input('col input 5')], + [sg.Text('col Row 7'), sg.Input('col input 6')]] + + layout = [[sg.Slider(range=(1,100), default_value=10, orientation='v', size=(8,20)), sg.Column(col)], + [sg.In('Last input')], + [sg.OK()]] + + # Display the window and get values + # If you're willing to not use the "context manager" design pattern, then it's possible + # to collapse the window display and read down to a single line of code. + event, values = sg.Window('Compact 1-line window with column').Layout(layout).Read() + + sg.Popup(event, values, line_width=200) + +The Column Element has 1 required parameter and 1 optional (the layout and the background color). Setting the background color has the same effect as setting the window's background color, except it only affects the column rectangle. + + Column(layout, background_color=None) + +The default background color for Columns is the same as the default window background color. If you change the look and feel of the window, the column background will match the window background automatically. + + + +---- +## Frame Element (Labelled Frames, Frames with a title) + +Frames work exactly the same way as Columns. You create layout that is then used to initialize the Frame. + + def Frame(title - the label / title to put on frame + layout - list of rows of elements the frame contains + title_color - color of the title text + background_color - color of background + title_location - locations to put the title + relief - type of relief to use + size - size of Frame in characters. Do not use if you want frame to autosize + font - font to use for title + pad - element padding to use when packing + border_width - how thick the line going around frame should be + key - key used to location the element + tooltip - tooltip text + + + +This code creates a window with a Frame and 2 buttons. + + frame_layout = [ + [sg.T('Text inside of a frame')], + [sg.CB('Check 1'), sg.CB('Check 2')], + ] + layout = [ + [sg.Frame('My Frame Title', frame_layout, font='Any 12', title_color='blue')], + [sg.Submit(), sg.Cancel()] + ] + + window = sg.Window('Frame with buttons', font=("Helvetica", 12)).Layout(layout) + + +![frame element](https://user-images.githubusercontent.com/13696193/45889173-c2245700-bd8d-11e8-8f73-1e5f1be3ddb1.jpg) + + + +Notice how the Frame layout looks identical to a window layout. A window works exactly the same way as a Column and a Frame. They all are "container elements". Elements that contain other elements. + +*These container Elements can be nested as deep as you want.* That's a pretty spiffy feature, right? Took a lot of work so be appreciative. Recursive code isn't trivial. + +## Canvas Element + +In my opinion, the tkinter Canvas Widget is the most powerful of the tkinter widget. While I try my best to completely isolate the user from anything that is tkinter related, the Canvas Element is the one exception. It enables integration with a number of other packages, often with spectacular results. + +### Matplotlib, Pyplot Integration + +One such integration is with Matploplib and Pyplot. There is a Demo program written that you can use as a design pattern to get an understanding of how to use the Canvas Widget once you get it. + + def Canvas(canvas - a tkinter canvasf if you created one. Normally not set + background_color - canvas color + size - size in pixels + pad - element padding for packing + key - key used to lookup element + tooltip - tooltip text + +The order of operations to obtain a tkinter Canvas Widget is: + + figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds + # define the window layout + layout = [[sg.Text('Plot test')], + [sg.Canvas(size=(figure_w, figure_h), key='canvas')], + [sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]] + + # create the window and show it without the plot + window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout).Finalize() + + + # add the plot to the window + fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig) + + # show it all again and get buttons + event, values = window.Read() + +To get a tkinter Canvas Widget from PySimpleGUI, follow these steps: +* Add Canvas Element to your window +* Layout your window +* Call `window.Finalize()` - this is a critical step you must not forget +* Find the Canvas Element by looking up using key +* Your Canvas Widget Object will be the found_element.TKCanvas +* Draw on your canvas to your heart's content +* Call `window.Read()` - Nothing will appear on your canvas until you call Read + +See `Demo_Matplotlib.py` for a Recipe you can copy. + + ### Canvas Methods TKCanvas - not a method but a property. Returns the tkinter Canvas Widget -## Graph Element - -All you math fans will enjoy this Element... and all you non-math fans will enjoy it too. - -I've found nothing to be less fun than dealing with a graphic's coordinate system from a GUI Framework. It's always upside down from what I want. (0,0) is in the upper left hand corner. In short, it's a **pain in the ass**. - -Graph Element to the rescue. A Graph Element creates a pixel addressable canvas using YOUR coordinate system. *You* get to define the units on the X and Y axis. - -There are 3 values you'll need to supply the Graph Element. They are: -* Size of the canvas in pixels -* The lower left (x,y) coordinate of your coordinate system -* The upper right (x,y) coordinate of your coordinate system - -After you supply those values you can scribble all of over your graph by creating Graph Figures. Graph Figures are created, and a Figure ID is obtained by calling: -* DrawCircle -* DrawLine -* DrawPoint -* DrawRectangle -* DrawOval - -You can move your figures around on the canvas by supplying the Figure ID the x,y amount to move. - - graph.MoveFigure(my_circle, 10, 10) - -This Element is relatively new and may have some parameter additions or deletions. It shouldn't break your code however. - - def Graph( canvas_size - size of canvas in pixels - graph_bottom_left - the x,y location of your coordinate system's bottom left point - graph_top_right - the x,y location of your coordinate system's top right point - background_color - color to use for background - pad - element padding for pack - key - key used to lookup element - tooltip - tooltip text - +## Graph Element + +All you math fans will enjoy this Element... and all you non-math fans will enjoy it too. + +I've found nothing to be less fun than dealing with a graphic's coordinate system from a GUI Framework. It's always upside down from what I want. (0,0) is in the upper left hand corner. In short, it's a **pain in the ass**. + +Graph Element to the rescue. A Graph Element creates a pixel addressable canvas using YOUR coordinate system. *You* get to define the units on the X and Y axis. + +There are 3 values you'll need to supply the Graph Element. They are: +* Size of the canvas in pixels +* The lower left (x,y) coordinate of your coordinate system +* The upper right (x,y) coordinate of your coordinate system + +After you supply those values you can scribble all of over your graph by creating Graph Figures. Graph Figures are created, and a Figure ID is obtained by calling: +* DrawCircle +* DrawLine +* DrawPoint +* DrawRectangle +* DrawOval + +You can move your figures around on the canvas by supplying the Figure ID the x,y amount to move. + + graph.MoveFigure(my_circle, 10, 10) + +This Element is relatively new and may have some parameter additions or deletions. It shouldn't break your code however. + + def Graph( canvas_size - size of canvas in pixels + graph_bottom_left - the x,y location of your coordinate system's bottom left point + graph_top_right - the x,y location of your coordinate system's top right point + background_color - color to use for background + pad - element padding for pack + key - key used to lookup element + tooltip - tooltip text + ### Graph Methods ```python DrawLine(self, point_from, point_to, color='black', width=1) @@ -2514,54 +2566,54 @@ Update - changes background color Move - moves everything an x,y direction MoveFigure - moves an individual figure - -## Table Element - -Let me say up front that the Table Element has Beta status. The reason is that some of the parameters are not quite right and will change. Be warned one or two parameters may change. The `size` parameter in particular is gong to change. Currently the number of rows to allocate for the table is set by the height parameter of size. The problem is that the width is not used. The plan is to instead have a parameter named `number_of_rows` or something like it. - - def Table(values - Your table's array - headings - list of strings representing your headings, if you have any - visible_column_map - list of bools. If True, column in that position is shown. Defaults to all columns - col_widths - list of column widths - def_col_width - default column width. defaults to 10 - auto_size_columns - bool. If True column widths are determined by table contents - max_col_width - maximum width of a column. defaults to 25 - select_mode - table rows can be selected, but doesn't currently do anything - display_row_numbers - bool. If True shows numbers next to rows - scrollable - if True table will be scrolled - font - font for table entries - justification - left, right, center - text_color - color of text - background_color - cell background color - size - (None, number of rows). - pad - element padding for packing - key - key used to lookup element - tooltip - tooltip text - - + +## Table Element + +Let me say up front that the Table Element has Beta status. The reason is that some of the parameters are not quite right and will change. Be warned one or two parameters may change. The `size` parameter in particular is gong to change. Currently the number of rows to allocate for the table is set by the height parameter of size. The problem is that the width is not used. The plan is to instead have a parameter named `number_of_rows` or something like it. + + def Table(values - Your table's array + headings - list of strings representing your headings, if you have any + visible_column_map - list of bools. If True, column in that position is shown. Defaults to all columns + col_widths - list of column widths + def_col_width - default column width. defaults to 10 + auto_size_columns - bool. If True column widths are determined by table contents + max_col_width - maximum width of a column. defaults to 25 + select_mode - table rows can be selected, but doesn't currently do anything + display_row_numbers - bool. If True shows numbers next to rows + scrollable - if True table will be scrolled + font - font for table entries + justification - left, right, center + text_color - color of text + background_color - cell background color + size - (None, number of rows). + pad - element padding for packing + key - key used to lookup element + tooltip - tooltip text + + ## Tree Element The Tree Element and Table Element are close cousins. Many of the parameters found in the Table Element apply to Tree Elements. In particular the heading information, column widths, etc. ``` class Tree(data=None - data in TreeData format - headings=None - list of strings representing your headings - visible_column_map=None - list of bools indicating which columns to display - col_widths=None - list of column widths - col0_width=10 - width of the first column which has the text data - def_col_width=10 - default column width - auto_size_columns=True - if true will autosize columns (currenly only sizes to col heading width) - max_col_width=20 - max width for columns in characters - select_mode=None - not yet used - font=None - the display font - justification='right' - justification for data display - text_color=None- color of text to display - background_color=None - background color - num_rows=None - number of rows to display - pad=None - element padding - key=None - key for element - tooltip=None - tooltip -``` + headings=None - list of strings representing your headings + visible_column_map=None - list of bools indicating which columns to display + col_widths=None - list of column widths + col0_width=10 - width of the first column which has the text data + def_col_width=10 - default column width + auto_size_columns=True - if true will autosize columns (currenly only sizes to col heading width) + max_col_width=20 - max width for columns in characters + select_mode=None - not yet used + font=None - the display font + justification='right' - justification for data display + text_color=None- color of text to display + background_color=None - background color + num_rows=None - number of rows to display + pad=None - element padding + key=None - key for element + tooltip=None - tooltip +``` Unlike Tables there is no standard format for trees. Thus the data structure passed to the Tree Element must be constructed. This is done using the TreeData class. The process is as follows: * Get a TreeData Object @@ -2578,97 +2630,97 @@ This code creates a TreeData object and populates with 3 values ```python treedata = sg.TreeData() -treedata.Insert("", '_A_', 'A', [1,2,3]) -treedata.Insert("", '_B_', 'B', [4,5,6]) +treedata.Insert("", '_A_', 'A', [1,2,3]) +treedata.Insert("", '_B_', 'B', [4,5,6]) treedata.Insert("_A_", '_A1_', 'A1', ['can','be','anything']) ``` Note that you can use the same values for display_text and keys. The only thing you have to watch for is that you cannot repeat keys. - -## Tab and Tab Group Elements - -Tabs have been a part of PySimpleGUI since the initial release. However, the initial implementation applied tabs at the top level only. The entire window had to be tabbed. There with other limitations that came along with that implementation. That all changed in version 3.8.0 with the new elements - Tab and TabGroup. The old implementation of Tabs was removed in version 3.8.0 as well. - -Tabs are another "Container Element". The other Container Elements include: -* Frame -* Column - -You layout a Frame in exactly the same way as a Frame or Column elements, by passing in a list of elements. - -How you place a Tab into a Window is different than Graph or Frame elements. You cannot place a tab directly into a Window's layout. It much first be placed into a TabGroup. The TabGroup can then be placed into the Window. - -Let's look at this Window as an example: - -![tabbed 1](https://user-images.githubusercontent.com/13696193/45992808-b10f6a80-c059-11e8-9746-ac71afd4d3d6.jpg) - -View of second tab: - -![tabbed 2](https://user-images.githubusercontent.com/13696193/45992809-b10f6a80-c059-11e8-94e6-3bf543c9b0bd.jpg) - - -First we have the Tab layout definitions. They mirror what you see in the screen shots. Tab 1 has 1 Text Element in it. Tab 2 has a Text and an Input Element. - - - tab1_layout = [[sg.T('This is inside tab 1')]] - - tab2_layout = [[sg.T('This is inside tab 2')], - [sg.In(key='in')]] - -The layout for the entire window looks like this: - - layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]])], - [sg.RButton('Read')]] - -The Window layout has the TabGroup and within the tab Group are the two Tab elements. - -One important thing to notice about all of these container Elements... they all take a "list of lists" at the layout. They all have a layout that starts with `[[` - -You will want to keep this `[[ ]]` construct in your head a you're debugging your tabbed windows. It's easy to overlook one or two necessary ['s - -As mentioned earlier, the old-style Tabs were limited to being at the Window-level only. In other words, the tabs were equal in size to the entire window. This is not the case with the "new-style" tabs. This is why you're not going to be upset when you discover your old code no longer works with the new PySimpleGUI release. It'll be worth the few moments it'll take to convert your code. - -Check out what's possible with the NEW Tabs! - -![tabs tabs tabs](https://user-images.githubusercontent.com/13696193/45993438-fd0fde80-c05c-11e8-9ed0-742f14d3070f.jpg) - - -Check out Tabs 7 and 8. We've got a Window with a Column containing Tabs 5 and 6. On Tab 6 are... Tabs 7 and 8. - -As of Release 3.8.0, not all of *options* shown in the API definitions of the Tab and TabGroup Elements are working. They are there as placeholders. - -The definition of a TabGroup is - - TabGroup(layout, - title_color=None - background_color=None - font=None - pad=None - border_width=None - change_submits = False - key=None - tooltip=None) - -The definition of a Tab Element is - - Tab(title, - layout, - title_color=None, - background_color=None, - font=None, - pad=None + +## Tab and Tab Group Elements + +Tabs have been a part of PySimpleGUI since the initial release. However, the initial implementation applied tabs at the top level only. The entire window had to be tabbed. There with other limitations that came along with that implementation. That all changed in version 3.8.0 with the new elements - Tab and TabGroup. The old implementation of Tabs was removed in version 3.8.0 as well. + +Tabs are another "Container Element". The other Container Elements include: +* Frame +* Column + +You layout a Frame in exactly the same way as a Frame or Column elements, by passing in a list of elements. + +How you place a Tab into a Window is different than Graph or Frame elements. You cannot place a tab directly into a Window's layout. It much first be placed into a TabGroup. The TabGroup can then be placed into the Window. + +Let's look at this Window as an example: + +![tabbed 1](https://user-images.githubusercontent.com/13696193/45992808-b10f6a80-c059-11e8-9746-ac71afd4d3d6.jpg) + +View of second tab: + +![tabbed 2](https://user-images.githubusercontent.com/13696193/45992809-b10f6a80-c059-11e8-94e6-3bf543c9b0bd.jpg) + + +First we have the Tab layout definitions. They mirror what you see in the screen shots. Tab 1 has 1 Text Element in it. Tab 2 has a Text and an Input Element. + + + tab1_layout = [[sg.T('This is inside tab 1')]] + + tab2_layout = [[sg.T('This is inside tab 2')], + [sg.In(key='in')]] + +The layout for the entire window looks like this: + + layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]])], + [sg.RButton('Read')]] + +The Window layout has the TabGroup and within the tab Group are the two Tab elements. + +One important thing to notice about all of these container Elements... they all take a "list of lists" at the layout. They all have a layout that starts with `[[` + +You will want to keep this `[[ ]]` construct in your head a you're debugging your tabbed windows. It's easy to overlook one or two necessary ['s + +As mentioned earlier, the old-style Tabs were limited to being at the Window-level only. In other words, the tabs were equal in size to the entire window. This is not the case with the "new-style" tabs. This is why you're not going to be upset when you discover your old code no longer works with the new PySimpleGUI release. It'll be worth the few moments it'll take to convert your code. + +Check out what's possible with the NEW Tabs! + +![tabs tabs tabs](https://user-images.githubusercontent.com/13696193/45993438-fd0fde80-c05c-11e8-9ed0-742f14d3070f.jpg) + + +Check out Tabs 7 and 8. We've got a Window with a Column containing Tabs 5 and 6. On Tab 6 are... Tabs 7 and 8. + +As of Release 3.8.0, not all of *options* shown in the API definitions of the Tab and TabGroup Elements are working. They are there as placeholders. + +The definition of a TabGroup is + + TabGroup(layout, + title_color=None + background_color=None + font=None + pad=None + border_width=None + change_submits = False + key=None + tooltip=None) + +The definition of a Tab Element is + + Tab(title, + layout, + title_color=None, + background_color=None, + font=None, + pad=None disabled=False - border_width=None - key=None - tooltip=None) - - -### Reading Tab Groups - -Tab Groups now return a value when a Read returns. They return which tab is currently selected. There is also a change_submits parameter that can be set that causes a Read to return if a Tab in that group is selected / changed. The key or title belonging to the Tab that was switched to will be returned as the value - - + border_width=None + key=None + tooltip=None) + + +### Reading Tab Groups + +Tab Groups now return a value when a Read returns. They return which tab is currently selected. There is also a change_submits parameter that can be set that causes a Read to return if a Tab in that group is selected / changed. The key or title belonging to the Tab that was switched to will be returned as the value + + ### Tab Element Methods ```python Update(disabled = None) @@ -2677,757 +2729,757 @@ WARNING - This Update method does not yet work! ## Colors -Starting in version 2.5 you can change the background colors for the window and the Elements. - -Your windows can go from this: - -![snap0155](https://user-images.githubusercontent.com/13696193/43273879-a9fdc10a-90cb-11e8-8c20-4f6a244ebe2f.jpg) - - -to this... with one function call... - - -![snap0156](https://user-images.githubusercontent.com/13696193/43273880-aa1955e6-90cb-11e8-94b6-673ecdb2698c.jpg) - - - -While you can do it on an element by element or window level basis, the easiest way, by far, is a call to `SetOptions`. - -Be aware that once you change these options they are changed for the rest of your program's execution. All of your windows will have that look and feel, until you change it to something else (which could be the system default colors. - -This call sets all of the different color options. - - SetOptions(background_color='#9FB8AD', - text_element_background_color='#9FB8AD', - element_background_color='#9FB8AD', - scrollbar_color=None, - input_elements_background_color='#F7F3EC', - progress_meter_color = ('green', 'blue') - button_color=('white','#475841')) - - - -# Global Settings -**Global Settings** -Let's have some fun customizing! Make PySimpleGUI look the way you want it to look. You can set the global settings using the function `PySimpleGUI.SetOptions`. Each option has an optional parameter that's used to set it. - - SetOptions(icon=None - button_color=(None,None) - element_size=(None,None), - margins=(None,None), - element_padding=(None,None) - auto_size_text=None - auto_size_buttons=None - font=None - border_width=None - slider_border_width=None - slider_relief=None - slider_orientation=None - autoclose_time=None - message_box_line_width=None - progress_meter_border_depth=None - progress_meter_style=None - progress_meter_relief=None - progress_meter_color=None - progress_meter_size=None - text_justification=None - text_color=None - background_color=None - element_background_color=None - text_element_background_color=None - input_elements_background_color=None - element_text_color=None - input_text_color=None - scrollbar_color=None, text_color=None - debug_win_size=(None,None) - window_location=(None,None) - tooltip_time = None - -Explanation of parameters - - icon - filename of icon used for taskbar and title bar - button_color - button color (foreground, background) - element_size - element size (width, height) in characters - margins - tkinter margins around outsize - element_padding - tkinter padding around each element - auto_size_text - autosize the elements to fit their text - auto_size_buttons - autosize the buttons to fit their text - font - font used for elements - border_width - amount of bezel or border around sunken or raised elements - slider_border_width - changes the way sliders look - slider_relief - changes the way sliders look - slider_orientation - changes orientation of slider - autoclose_time - time in seconds for autoclose boxes - message_box_line_width - number of characers in a line of text in message boxes - progress_meter_border_depth - amount of border around raised or lowered progress meters - progress_meter_style - style of progress meter as defined by tkinter - progress_meter_relief - relief style - progress_meter_color - color of the bar and background of progress meters - progress_meter_size - size in (characters, pixels) - background_color - Color of the main window's background - element_background_color - Background color of the elements - text_element_background_color - Text element background color - input_elements_background_color - Input fields background color - element_text_color - Text color of elements that have text, like Radio Buttons - input_text_color - Color of the text that you type in - scrollbar_color - Color for scrollbars (may not always work) - text_color - Text element default text color - text_justification - justification to use on Text Elements. Values are strings - 'left', 'right', 'center' - debug_win_size - size of the Print output window - window_location - location on the screen (x,y) of window's top left cornder - tooltip_time - time in milliseconds to wait before showing a tooltip. Default is 400ms - - -These settings apply to all windows `SetOptions`. The Row options and Element options will take precedence over these settings. Settings can be thought of as levels of settings with the window-level being the highest and the Element-level the lowest. Thus the levels are: - - - window level - - Row level - - Element level - -Each lower level overrides the settings of the higher level. Once settings have been changed, they remain changed for the duration of the program (unless changed again). - -# Persistent windows (Window stays open after button click) - -There are 2 ways to keep a window open after the user has clicked a button. One way is to use non-blocking windows (see the next section). The other way is to use buttons that 'read' the window instead of 'close' the window when clicked. The typical buttons you find in windows, including the shortcut buttons, close the window. These include OK, Cancel, Submit, etc. The Button Element also closes the window. - -The `RButton` Element creates a button that when clicked will return control to the user, but will leave the window open and visible. This button is also used in Non-Blocking windows. The difference is in which call is made to read the window. The `Read` call will block, the `ReadNonBlocking` will not block. - - - -## Asynchronous (Non-Blocking) windows -So you want to be a wizard do ya? Well go boldly! - -Use async windows sparingly. It's possible to have a window that appears to be async, but it is not. **Please** try to find other methods before going to async windows. The reason for this plea is that async windows poll tkinter over and over. If you do not have a sleep in your loop, you will eat up 100% of the CPU time. - -When to use a non-blocking window: -* A media file player like an MP3 player -* A status dashboard that's periodically updated -* Progress Meters - when you want to make your own progress meters -* Output using print to a scrolled text element. Good for debugging. - -If your application doesn't follow the basic design pattern at one of those, then it shouldn't be executed as a non-blocking window. - - -### Instead of ReadNonBlocking --- Use `change_submits = True` or return_keyboard_events = True - -Any time you are thinking "I want an X Element to cause a Y Element to do something", then you want to use the `change_submits` option. - -***Instead of polling, try options that cause the window to return to you.*** By using non-blocking windows, you are *polling*. You can indeed create your application by polling. It will work. But you're going to be maxing out your processor and may even take longer to react to an event than if you used another technique. - -**Examples** - -One example is you have an input field that changes as you press buttons on an on-screen keypad. - -![keypad 3](https://user-images.githubusercontent.com/13696193/45260275-a2198e80-b3b0-11e8-85fe-a4ce6484510f.jpg) - - - - -### Periodically Calling`ReadNonBlocking` - -Periodically "refreshing" the visible GUI. The longer you wait between updates to your GUI the more sluggish your windows will feel. It is up to you to make these calls or your GUI will freeze. - -There are 2 methods of interacting with non-blocking windows. -1. Read the window just as you would a normal window -2. "Refresh" the window's values without reading the window. It's a quick operation meant to show the user the latest values - - With asynchronous windows the window is shown, user input is read, but your code keeps right on chugging. YOUR responsibility is to call `PySimpleGUI.ReadNonBlocking` on a periodic basis. Once a second or more will produce a reasonably snappy GUI. - - #### Exiting a Non-Blocking window - -It's important to always provide a "way out" for your user. Make sure you have provided a button or some other mechanism to exit. Also be sure to check for closed windows in your code. It is possible for a window to look closed, but continue running your event loop. - -Typically when reading a window you check `if Button is None` to determine if a window was closed. With NonBlocking windows, buttons will be None unless a button or a key was returned. The way you determine if a window was closed in a non-blocking window is to check **both** the button and the values are None. Since button is normally None, you only need to test for `value is None` in your code. - -The proper code to check if the user has exited the window will be a polling-loop that looks something like this: - - while True: - button, values = window.ReadNonBlocking() - if values is None or button == 'Quit': - break - - -We're going to build an app that does the latter. It's going to update our window with a running clock. - -The basic flow and functions you will be calling are: -Setup - - window = Window() - window_rows = ..... - window.Layout(window_rows, non_blocking=True).Read() - - -Periodic refresh - - window.ReadNonBlocking() or window.Refresh() - -If you need to close the window - - window.CloseNonBlocking() - -Rather than the usual `window.Layout().Read()` call, we're manually adding the rows (doing the layout) and then showing the window. After the window is shown, you simply call `window.ReadNonBlocking()` every now and then. - -When you are ready to close the window (assuming the window wasn't closed by the user or a button click) you simply call `window.CloseNonBlocking()` - -**Example - Running timer that updates** -See the sample code on the GitHub named Demo Media Player for another example of Async windows. We're going to make a window and update one of the elements of that window every .01 seconds. Here's the entire code to do that. - - - import PySimpleGUI as sg - import time - - # window that doesn't block - # Make a window, but don't use context manager - window = sg.Window('Running Timer', auto_size_text=True) - - # Create the layout - window_rows = [[sg.Text('Non-blocking GUI with updates')], - [sg.Text('', size=(8, 2), font=('Helvetica', 20), key='output') ], - [sg.Button('Quit')]] - # Layout the rows of the window and perform a read. Indicate the window is non-blocking! - window.Layout(window_rows).ReadNonBlocking() - - # - # Some place later in your code... - # You need to perform a ReadNonBlocking on your window every now and then or - # else it won't refresh - # - - for i in range(1, 1000): - window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i / 100), 60), i % 100)) - button, values = window.ReadNonBlocking() - if values is None or button == 'Quit': - break - time.sleep(.01) - else: - window.CloseNonBlocking() - - -What we have here is the same sequence of function calls as in the description. Get a window, add rows to it, show the window, and then refresh it every now and then. - -The new thing in this example is the call use of the Update method for the Text Element. The first thing we do inside the loop is "update" the text element that we made earlier. This changes the value of the text field on the window. The new value will be displayed when `window.ReadNonBlocking()` is called. if you want to have the window reflect your changes immediately, call `window.Refresh()`. - -Note the `else` statement on the for loop. This is needed because we're about to exit the loop while the window is still open. The user has not closed the window using the X nor a button so it's up to the caller to close the window using `CloseNonBlocking`. +Starting in version 2.5 you can change the background colors for the window and the Elements. - +Your windows can go from this: -# Updating Elements (changing elements in active window) - -Persistent windows remain open and thus continue to interact with the user after the Read has returned. Often the program wishes to communicate results (output information) or change an Element's values (such as populating a List Element). - -You can use Update to do things like: -* Have one Element (appear to) make a change to another Element -* Disable a button, slider, input field, etc -* Change a button's text -* Change an Element's text or background color -* Add text to a scrolling output window -* Change the choices in a list -* etc - -The way this is done is via an Update method that is available for nearly all of the Elements. Here is an example of a program that uses a persistent window that is updated. - -![snap0272](https://user-images.githubusercontent.com/13696193/45260249-ec4e4000-b3af-11e8-853b-9b29d0bf7797.jpg) - - -In some programs these updates happen in response to another Element. This program takes a Spinner and a Slider's input values and uses them to resize a Text Element. The Spinner and Slider are on the left, the Text element being changed is on the right. - - - # Testing async window, see if can have a slider - # that adjusts the size of text displayed - - import PySimpleGUI as sg - fontSize = 12 - layout = [[sg.Spin([sz for sz in range(6, 172)], font=('Helvetica 20'), initial_value=fontSize, change_submits=True, key='spin'), - sg.Slider(range=(6,172), orientation='h', size=(10,20), - change_submits=True, key='slider', font=('Helvetica 20')), - sg.Text("Aa", size=(2, 1), font="Helvetica " + str(fontSize), key='text')]] - - sz = fontSize - window = sg.Window("Font size selector", grab_anywhere=False).Layout(layout) - # Event Loop - while True: - button, values= window.Read() - if button is None: - break - sz_spin = int(values['spin']) - sz_slider = int(values['slider']) - sz = sz_spin if sz_spin != fontSize else sz_slider - if sz != fontSize: - fontSize = sz - font = "Helvetica " + str(fontSize) - window.FindElement('text').Update(font=font) - window.FindElement('slider').Update(sz) - window.FindElement('spin').Update(sz) - - print("Done.") - - -Inside the event loop we read the value of the Spinner and the Slider using those Elements' keys. -For example, `values['slider']` is the value of the Slider Element. - -This program changes all 3 elements if either the Slider or the Spinner changes. This is done with these statements: - - window.FindElement('text').Update(font=font) - window.FindElement('slider').Update(sz) - window.FindElement('spin').Update(sz) - -Remember this design pattern because you will use it OFTEN if you use persistent windows. - -It works as follows. The call to `window.FindElement` returns the Element object represented by they provided `key`. This element is then updated by calling it's `Update` method. This is another example of Python's "chaining" feature. We could write this code using the long-form: - - text_element = window.FindElement('text') - text_element.Update(font=font) - -The takeaway from this exercise is that keys are key in PySimpleGUI's design. They are used to both read the values of the window and also to identify elements. As already mentioned, they are used as targets in Button calls. - - ### Updating Multiple Elements - If you have a large number of Elements to update, you can call `Window.UpdateElements()`. - -` UpdateElements(key_list, - value_list)` - -`key_list` - list of keys for elements you wish to update -`value_list` - list of values, one for each key - - window.UpdateElements(('name', 'address', 'phone'), ('Fred Flintstone', '123 Rock Quarry Road', '555#')) - - -# Keyboard & Mouse Capture -Beginning in version 2.10 you can capture keyboard key presses and mouse scroll-wheel events. Keyboard keys can be used, for example, to detect the page-up and page-down keys for a PDF viewer. To use this feature, there's a boolean setting in the Window call `return_keyboard_events` that is set to True in order to get keys returned along with buttons. - -Keys and scroll-wheel events are returned in exactly the same way as buttons. - -For scroll-wheel events, if the mouse is scrolled up, then the `button` text will be `MouseWheel:Up`. For downward scrolling, the text returned is `MouseWheel:Down` - -Keyboard keys return 2 types of key events. For "normal" keys (a,b,c, etc), a single character is returned that represents that key. Modifier and special keys are returned as a string with 2 parts: - - Key Sym:Key Code - -Key Sym is a string such as 'Control_L'. The Key Code is a numeric representation of that key. The left control key, when pressed will return the value 'Control_L:17' - - import PySimpleGUI as sg - - # Recipe for getting keys, one at a time as they are released - # If want to use the space bar, then be sure and disable the "default focus" - - with sg.Window("Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: - text_elem = sg.Text("", size=(18,1)) - layout = [[sg.Text("Press a key or scroll mouse")], - [text_elem], - [sg.Button("OK")]] - - window.Layout(layout) - # ---===--- Loop taking in user input --- # - while True: - button, value = window.ReadNonBlocking() - - if button == "OK" or (button is None and value is None): - print(button, "exiting") - break - if button is not None: - text_elem.Update(button) - -You want to turn off the default focus so that there no buttons that will be selected should you press the spacebar. - -### Realtime Keyboard Capture -Use realtime keyboard capture by calling - - import PySimpleGUI as sg - - with sg.Window("Realtime Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: - layout = [[sg.Text("Hold down a key")], - [sg.Button("OK")]] - - window.Layout(layout) - - while True: - button, value = window.ReadNonBlocking() - - if button == "OK": - print(button, value, "exiting") - break - if button is not None: - print(button) - elif value is None: - break - -# Menus - -Beginning in version 3.01 you can add a menubar to your window. You specify the menus in much the same way as you do window layouts, with lists. Menu selections are returned as button clicks, so be aware of your overall naming conventions. If you have an Exit button and also an Exit menu option, then you won't be able to tell the difference when your window.Read returns. Hopefully will not be a problem. - -This definition: - - menu_def = [['File', ['Open', 'Save', 'Exit',]], - ['Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], - ['Help', 'About...'],] - -Note the placement of ',' and of []. It's tricky to get the nested menus correct that implement cascading menus. See how paste has Special and Normal as a list after it. This means that Paste has a cascading menu with items Special and Normal. - -They menu_def layout produced this window: - -![menu](https://user-images.githubusercontent.com/13696193/45306723-56b7cb00-b4eb-11e8-8cbd-faef0c90f8b4.jpg) - - -You have used ALT-key in other Windows programs to navigate menus. For example Alt-F+X exits the program. The Alt-F pulls down the File menu. The X selects the entry marked Exit. - -The good news is that PySimpleGUI allows you to create the same kind of menus! Your program can play with the big-boys. And, it's trivial to do. - -All that's required is for your to add an "&" in front of the letter you want to appear with an underscore. When you hold the Alt key down you will see the menu with underlines that you marked. - -One other little bit of polish you can add are separators in your list. To add a line in your list of menu choices, create a menu entry that looks like this: ` '---'` - -This is an example Menu with underlines and a separator. - -``` -# ------ Menu Definition ------ # -menu_def = [['&File', ['&Open', '&Save', '---', 'Properties', 'E&xit' ]], - ['&Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], - ['&Help', '&About...'],] -``` - And this is the spiffy menu it produced: - ![menus with shortcuts](https://user-images.githubusercontent.com/13696193/46251674-f5b74f00-c427-11e8-95c6-547adc59041b.jpg) - - +![snap0155](https://user-images.githubusercontent.com/13696193/43273879-a9fdc10a-90cb-11e8-8c20-4f6a244ebe2f.jpg) -# Sample Applications - -Use the example programs as a starting basis for your GUI. Copy, paste, modify and run! The demo files are: - - | Source File| Description | -|--|--| -|**Demo_All_Widgets.py**| Nearly all of the Elements shown in a single window -|**Demo_Borderless_Window.py**| Create clean looking windows with no border -|**Demo_Button_States.py**| One way of implementing disabling of buttons -|**Demo_Calendar.py** | Demo of the Calendar Chooser button -|**Demo_Canvas.py** | window with a Canvas Element that is updated outside of the window -|**Demo_Chat.py** | A chat window with scrollable history -|**Demo_Chatterbot.py** | Front-end to Chatterbot Machine Learning project -|**Demo_Color.py** | How to interact with color using RGB hex values and named colors -|**Demo_Columns.py** | Using the Column Element to create more complex windows -|**Demo_Compare_Files.py** | Using a simple GUI front-end to create a compare 2-files utility -|**Demo_Cookbook_Browser.py** | Source code browser for all Recipes in Cookbook -|**Demo_Dictionary.py** | Specifying and using return values in dictionary format -**Demo_DOC_Viewer_PIL.py** | Display a PDF, HTML, ebook file, etc in your window -|**Demo_DisplayHash1and256.py** | Using high level API and custom window to implement a simple display hash code utility -|**Demo_DuplicateFileFinder.py** | High level API used to get a folder that is used by utility that finds duplicate files. Uses progress meter to show progress. 2 lines of code required to add GUI and meter -|**Demo_Fill_Form.py** | How to perform a bulk-fill for a window. Saving and loading a window from disk -|**Demo Font Sizer.py** | Demonstrates Elements updating other Elements -|**Demo_Func_Callback_Simulator.py** | For the Raspberry Pi crowd. Event loop that simulates traditional GUI callback functions should you already have an architecture that uses them -|**Demo_GoodColors.py** | Using some of the pre-defined PySimpleGUI individual colors -|**Demo_HowDoI.py** | This is a utility to be experienced! It will change how you code -|**Demo_Img_Viewer.py** | Display jpg, png,tiff, bmp files -|**Demo_Keyboard.py** | Using blocking keyboard events -|**Demo_Keyboard_Realtime.py** | Using non-blocking / realtime keyboard events -|**Demo_Machine_Learning.py** | A sample Machine Learning front end -|**Demo_Matplotlib.py** | Integrating with Matplotlib to create a single graph -|**Demo_Matplotlib_Animated.py** | Animated Matplotlib line graph -|**Demo_Matplotlib_Animated_Scatter.py** | Animated Matplotlib scatter graph -|**Demo_Matplotlib_Browser.py** | Browse Matplotlib gallery -|**Demo_Media_Player.py** | Non-blocking window with a media player layout. Demonstrates button graphics, Update method -|**Demo_MIDI_Player.py** | GUI wrapper for Mido MIDI package. Functional MIDI player that controls attached MIDI devices -|**Demo_NonBlocking_Form.py** | a basic async window -|**Demo_OpenCV.py** | Integrated with OpenCV -|**Demo_Password_Login** | Password protection using SHA1 -|**Demo_PDF_Viewer.py** | Submitted by a user! Previews PDF documents. Uses keyboard input & mouse scrollwheel to navigate -|**Demo_Pi_LEDs.py** | Control GPIO using buttons -|**Demo_Pi_Robotics.py** | Simulated robot control using realtime buttons -|**Demo_PNG_Vierwer.py** | Uses Image Element to display PNG files -| **Demo_Progress_Meters.py** | Demonstrates using 2 progress meters simultaneously -|**Demo_Recipes.py** | A collection of various Recipes. Note these are not the same as the Recipes in the Recipe Cookbook -|**Demo_Script_Launcher.py** | Demonstrates one way of adding a front-end onto several command line scripts -|**Demo_Script_Parameters.py** | Add a 1-line GUI to the front of your previously command-line only scripts -|**Demo_Tabbed_Form.py** | Using the Tab feature -|**Demo_Table_Simulation.py** | Use input fields to display and edit tables -|**Demo_Timer.py** | Simple non-blocking window - -## Packages Used In Demos - - - While the core PySimpleGUI code does not utilize any 3rd party packages, some of the demos do. They add a GUI to a few popular packages. These packages include: - * [Chatterbot](https://github.com/gunthercox/ChatterBot) - * [Mido](https://github.com/olemb/mido) - * [Matplotlib](https://matplotlib.org/) - * [PyMuPDF](https://github.com/rk700/PyMuPDF) - - -# Creating a Windows .EXE File - -It's possible to create a single .EXE file that can be distributed to Windows users. There is no requirement to install the Python interpreter on the PC you wish to run it on. Everything it needs is in the one EXE file, assuming you're running a somewhat up to date version of Windows. - -Installation of the packages, you'll need to install PySimpleGUI and PyInstaller (you need to install only once) - -``` -pip install PySimpleGUI -pip install PyInstaller - -``` - -To create your EXE file from your program that uses PySimpleGUI, `my_program.py`, enter this command in your Windows command prompt: - -``` -pyinstaller -wF my_program.py - -``` - -You will be left with a single file, `my_program.exe`, located in a folder named `dist` under the folder where you executed the `pyinstaller` command. - -That's all... Run your `my_program.exe` file on the Windows machine of your choosing. - -> "It's just that easy." - -(famous last words that screw up just about anything being referenced) - -Your EXE file should run without creating a "shell window". Only the GUI window should show up on your taskbar. - -If you get a crash with something like: -``` -ValueError: script '.......\src\tkinter' not found -``` - -Then try adding **`--hidden-import tkinter`** to your command - - - -## Fun Stuff -Here are some things to try if you're bored or want to further customize - -**Debug Output** -Be sure and check out the EasyPrint (Print) function described in the high-level API section. Leave your code the way it is, route your stdout and stderror to a scrolling window. - -For a fun time, add these lines to the top of your script - - import PySimpleGUI as sg - print = sg.Print - -This will turn all of your print statements into prints that display in a window on your screen rather than to the terminal. - -**Look and Feel** -Dial in the look and feel that you like with the `SetOptions` function. You can change all of the defaults in one function call. One line of code to customize the entire GUI. -Or beginning in version 2.9 you can choose from a look and feel using pre-defined color schemes. Call ChangeLookAndFeel with a description string. - - sg.ChangeLookAndFeel('GreenTan') - -Valid values for the description string are: - - GreenTan - LightGreen - BluePurple - Purple - BlueMono - GreenMono - BrownBlue - BrightColors - NeutralBlue - Kayak - SandyBeach - TealMono - -To see the latest list of color choices, take a look at the bottom of the `PySimpleGUI.py` file where you'll find the `ChangLookAndFeel` function. - -You can also combine the `ChangeLookAndFeel` function with the `SetOptions` function to quickly modify one of the canned color schemes. Maybe you like the colors but was more depth to your bezels. You can dial in exactly what you want. - -**ObjToString** -Ever wanted to easily display an objects contents easily? Use ObjToString to get a nicely formatted recursive walk of your objects. -This statement: - - print(sg.ObjToSting(x)) - -And this was the output - - - abc = abc - attr12 = 12 - c = - b = - a = - attr1 = 1 - attr2 = 2 - attr3 = three - attr10 = 10 - attrx = x - -You'll quickly wonder how you ever coded without it. - ---- -# Known Issues -While not an "issue" this is a ***stern warning*** - -## **Do not attempt** to call `PySimpleGUI` from multiple threads! It's `tkinter` based and `tkinter` has issues with multiple threads - -**Progress Meters** - the visual graphic portion of the meter may be off. May return to the native tkinter progress meter solution in the future. Right now a "custom" progress meter is used. On the bright side, the statistics shown are extremely accurate and can tell you something about the performance of your code. If you are running 2 or more progress meters at the same time using `OneLineProgressMeter`, you need to close the meter by using the "Cancel" button rather than the X - -**Async windows** - these include the 'easy' windows (`OneLineProgressMeter` and EasyPrint/Print). If you start overlapping having Async windows open with normal windows then things get a littler squirrelly. Still tracking down the issues and am making it more solid every day possible. You'll know there's an issue when you see blank window. - -**EasyPrint** - EasyPrint is a new feature that's pretty awesome. You print and the output goes to a window, with a scroll bar, that you can copy and paste from. Being a new feature, it's got some potential problems. There are known interaction problems with other GUI windows. For example, closing a Print window can also close other windows you have open. For now, don't close your debug print window until other windows are closed too. - -## Contributing - -A MikeTheWatchGuy production... entirely responsible for this code.... unless it causes you trouble in which case I'm not at all responsible. - -## Versions -|Version | Description | -|--|--| -| 1.0.9 | July 10, 2018 - Initial Release | -| 1.0.21 | July 13, 2018 - Readme updates | -| 2.0.0 | July 16, 2018 - ALL optional parameters renamed from CamelCase to all_lower_case -| 2.1.1 | July 18, 2018 - Global settings exposed, fixes -| 2.2.0| July 20, 2018 - Image Elements, Print output -| 2.3.0 | July 23, 2018 - Changed form.Read return codes, Slider Elements, Listbox element. Renamed some methods but left legacy calls in place for now. -| 2.4.0 | July 24, 2018 - Button images. Fixes so can run on Raspberry Pi -| 2.5.0 | July 26, 2018 - Colors. Listbox scrollbar. tkinter Progress Bar instead of homegrown. -| 2.6.0 | July 27, 2018 - auto_size_button setting. License changed to LGPL 3+ -| 2.7.0 | July 30, 2018 - realtime buttons, window_location default setting -| 2.8.0 | Aug 9, 2018 - New None default option for Checkbox element, text color option for all elements, return values as a dictionary, setting focus, binding return key -| 2.9.0 | Aug 16,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, colored text defaults -| 2.10.0 | Aug 25, 2018 - Keyboard & Mouse features (Return individual keys as if buttons, return mouse scroll-wheel as button, bind return-key to button, control over keyboard focus), SaveAs Button, Update & Get methods for InputText, Update for Listbox, Update & Get for Checkbox, Get for Multiline, Color options for Text Element Update, Progess bar Update can change max value, Update for Button to change text & colors, Update for Image Element, Update for Slider, Form level text justification, Turn off default focus, scroll bar for Listboxes, Images can be from filename or from in-RAM, Update for Image). Fixes - text wrapping in buttons, msg box, removed slider borders entirely and others -| 2.11.0 | Aug 29, 2018 - Lots of little changes that are needed for the demo programs to work. Buttons have their own default element size, fix for Mac default button color, padding support for all elements, option to immediately return if list box gets selected, FilesBrowse button, Canvas Element, Frame Element, Slider resolution option, Form.Refresh method, better text wrapping, 'SystemDefault' look and feel settin -| 2.20.0 | Sept 4, 2018 - Some sizable features this time around of interest to advanced users. Renaming of the MsgBox functions to Popup. Renaming GetFile, etc, to PopupGetFile. High-level windowing capabilities start with Popup, PopupNoWait/PopupNonblocking, PopupNoButtons, default icon, change_submits option for Listbox/Combobox/Slider/Spin/, New OptionMenu element, updating elements after shown, system defaul color option for progress bars, new button type (Dummy Button) that only closes a window, SCROLLABLE Columns!! (yea, playing in the Big League now), LayoutAndShow function removed, form.Fill - bulk updates to forms, FindElement - find element based on key value (ALL elements have keys now), no longer use grid packing for row elements (a potentially huge change), scrolled text box sizing changed, new look and feel themes (Dark, Dark2, Black, Tan, TanBlue, DarkTanBlue, DarkAmber, DarkBlue, Reds, Green) -| 2.30.0 | Sept 6, 2018 - Calendar Chooser (button), borderless windows, load/save form to disk -| 3.0.0 | Sept 7, 2018 - The "fix for poor choice of 2.x numbers" release. Color Chooser (button), "grab anywhere" windows are on by default, disable combo boxes, Input Element text justification (last part needed for 'tables'), Image Element changes to support OpenCV?, PopupGetFile and PopupGetFolder have better no_window option -| 3.01.01 | Sept 10, 2018 - Menus! (sort of a big deal) -| 3.01.02 | Step 11, 2018 - All Element.Update functions have a `disabled` parameter so they can be disabled. Renamed some parameters in Update function (sorry if I broke your code), fix for bug in Image.Update. Wasn't setting size correctly, changed grab_anywhere logic again,added grab anywhere option to PupupGetText (assumes disabled) -| 3.02.00 | Sept 14, 2018 - New Table Element (Beta release), MsgBox removed entirely, font setting for InputText Element, **packing change** risky change that allows some Elements to be resized,removed command parameter from Menu Element, new function names for ReadNonBlocking (Finalize, PreRead), change to text element autosizing and wrapping (yet again), lots of parameter additions to Popup functions (colors, etc). -| 3.03.00 | New feature - One Line Progress Meters, new display_row_numbers for Table Element, fixed bug in EasyProgresssMeters (function will soon go away), OneLine and Easy progress meters set to grab anywhere but can be turned off. -| 03,04.00 | Sept 18, 2018 - New features - Graph Element, Frame Element, more settings exposed to Popup calls. See notes below for more. -| 03.04.01 | Sept 18, 2018 - See release notes -| 03.05.00 | Sept 20, 2018 - See release notes -| 03.05.01 | Sept 22, 2018 - See release notes -| 03.05.02 | Sept 23, 2018 - See release notes -| 03.06.00 | Sept 23, 2018 - Goodbye FlexForm, hello Window -| 03.08.00 | Sept 25, 2018 - Tab and TabGroup Elements\ -| 01.00.00 for 2.7 | Sept 25, 2018 - First release for 2.7 -| 03.08.04 | Sept 30, 2018 - See release notes +to this... with one function call... + + +![snap0156](https://user-images.githubusercontent.com/13696193/43273880-aa1955e6-90cb-11e8-94b6-673ecdb2698c.jpg) + + + +While you can do it on an element by element or window level basis, the easiest way, by far, is a call to `SetOptions`. + +Be aware that once you change these options they are changed for the rest of your program's execution. All of your windows will have that look and feel, until you change it to something else (which could be the system default colors. + +This call sets all of the different color options. + + SetOptions(background_color='#9FB8AD', + text_element_background_color='#9FB8AD', + element_background_color='#9FB8AD', + scrollbar_color=None, + input_elements_background_color='#F7F3EC', + progress_meter_color = ('green', 'blue') + button_color=('white','#475841')) + + + +# Global Settings +**Global Settings** +Let's have some fun customizing! Make PySimpleGUI look the way you want it to look. You can set the global settings using the function `PySimpleGUI.SetOptions`. Each option has an optional parameter that's used to set it. + + SetOptions(icon=None + button_color=(None,None) + element_size=(None,None), + margins=(None,None), + element_padding=(None,None) + auto_size_text=None + auto_size_buttons=None + font=None + border_width=None + slider_border_width=None + slider_relief=None + slider_orientation=None + autoclose_time=None + message_box_line_width=None + progress_meter_border_depth=None + progress_meter_style=None + progress_meter_relief=None + progress_meter_color=None + progress_meter_size=None + text_justification=None + text_color=None + background_color=None + element_background_color=None + text_element_background_color=None + input_elements_background_color=None + element_text_color=None + input_text_color=None + scrollbar_color=None, text_color=None + debug_win_size=(None,None) + window_location=(None,None) + tooltip_time = None + +Explanation of parameters + + icon - filename of icon used for taskbar and title bar + button_color - button color (foreground, background) + element_size - element size (width, height) in characters + margins - tkinter margins around outsize + element_padding - tkinter padding around each element + auto_size_text - autosize the elements to fit their text + auto_size_buttons - autosize the buttons to fit their text + font - font used for elements + border_width - amount of bezel or border around sunken or raised elements + slider_border_width - changes the way sliders look + slider_relief - changes the way sliders look + slider_orientation - changes orientation of slider + autoclose_time - time in seconds for autoclose boxes + message_box_line_width - number of characers in a line of text in message boxes + progress_meter_border_depth - amount of border around raised or lowered progress meters + progress_meter_style - style of progress meter as defined by tkinter + progress_meter_relief - relief style + progress_meter_color - color of the bar and background of progress meters + progress_meter_size - size in (characters, pixels) + background_color - Color of the main window's background + element_background_color - Background color of the elements + text_element_background_color - Text element background color + input_elements_background_color - Input fields background color + element_text_color - Text color of elements that have text, like Radio Buttons + input_text_color - Color of the text that you type in + scrollbar_color - Color for scrollbars (may not always work) + text_color - Text element default text color + text_justification - justification to use on Text Elements. Values are strings - 'left', 'right', 'center' + debug_win_size - size of the Print output window + window_location - location on the screen (x,y) of window's top left cornder + tooltip_time - time in milliseconds to wait before showing a tooltip. Default is 400ms + + +These settings apply to all windows `SetOptions`. The Row options and Element options will take precedence over these settings. Settings can be thought of as levels of settings with the window-level being the highest and the Element-level the lowest. Thus the levels are: + + - window level + - Row level + - Element level + +Each lower level overrides the settings of the higher level. Once settings have been changed, they remain changed for the duration of the program (unless changed again). + +# Persistent windows (Window stays open after button click) + +There are 2 ways to keep a window open after the user has clicked a button. One way is to use non-blocking windows (see the next section). The other way is to use buttons that 'read' the window instead of 'close' the window when clicked. The typical buttons you find in windows, including the shortcut buttons, close the window. These include OK, Cancel, Submit, etc. The Button Element also closes the window. + +The `RButton` Element creates a button that when clicked will return control to the user, but will leave the window open and visible. This button is also used in Non-Blocking windows. The difference is in which call is made to read the window. The `Read` call will block, the `ReadNonBlocking` will not block. + + + +## Asynchronous (Non-Blocking) windows +So you want to be a wizard do ya? Well go boldly! + +Use async windows sparingly. It's possible to have a window that appears to be async, but it is not. **Please** try to find other methods before going to async windows. The reason for this plea is that async windows poll tkinter over and over. If you do not have a sleep in your loop, you will eat up 100% of the CPU time. + +When to use a non-blocking window: +* A media file player like an MP3 player +* A status dashboard that's periodically updated +* Progress Meters - when you want to make your own progress meters +* Output using print to a scrolled text element. Good for debugging. + +If your application doesn't follow the basic design pattern at one of those, then it shouldn't be executed as a non-blocking window. + + +### Instead of ReadNonBlocking --- Use `change_submits = True` or return_keyboard_events = True + +Any time you are thinking "I want an X Element to cause a Y Element to do something", then you want to use the `change_submits` option. + +***Instead of polling, try options that cause the window to return to you.*** By using non-blocking windows, you are *polling*. You can indeed create your application by polling. It will work. But you're going to be maxing out your processor and may even take longer to react to an event than if you used another technique. + +**Examples** + +One example is you have an input field that changes as you press buttons on an on-screen keypad. + +![keypad 3](https://user-images.githubusercontent.com/13696193/45260275-a2198e80-b3b0-11e8-85fe-a4ce6484510f.jpg) + + + + +### Periodically Calling`ReadNonBlocking` + +Periodically "refreshing" the visible GUI. The longer you wait between updates to your GUI the more sluggish your windows will feel. It is up to you to make these calls or your GUI will freeze. + +There are 2 methods of interacting with non-blocking windows. +1. Read the window just as you would a normal window +2. "Refresh" the window's values without reading the window. It's a quick operation meant to show the user the latest values + + With asynchronous windows the window is shown, user input is read, but your code keeps right on chugging. YOUR responsibility is to call `PySimpleGUI.ReadNonBlocking` on a periodic basis. Once a second or more will produce a reasonably snappy GUI. + + #### Exiting a Non-Blocking window + +It's important to always provide a "way out" for your user. Make sure you have provided a button or some other mechanism to exit. Also be sure to check for closed windows in your code. It is possible for a window to look closed, but continue running your event loop. + +Typically when reading a window you check `if Button is None` to determine if a window was closed. With NonBlocking windows, buttons will be None unless a button or a key was returned. The way you determine if a window was closed in a non-blocking window is to check **both** the button and the values are None. Since button is normally None, you only need to test for `value is None` in your code. + +The proper code to check if the user has exited the window will be a polling-loop that looks something like this: + + while True: + event, values = window.ReadNonBlocking() + if values is None or event == 'Quit': + break + + +We're going to build an app that does the latter. It's going to update our window with a running clock. + +The basic flow and functions you will be calling are: +Setup + + window = Window() + window_rows = ..... + window.Layout(window_rows, non_blocking=True).Read() + + +Periodic refresh + + window.ReadNonBlocking() or window.Refresh() + +If you need to close the window + + window.CloseNonBlocking() + +Rather than the usual `window.Layout().Read()` call, we're manually adding the rows (doing the layout) and then showing the window. After the window is shown, you simply call `window.ReadNonBlocking()` every now and then. + +When you are ready to close the window (assuming the window wasn't closed by the user or a button click) you simply call `window.CloseNonBlocking()` + +**Example - Running timer that updates** +See the sample code on the GitHub named Demo Media Player for another example of Async windows. We're going to make a window and update one of the elements of that window every .01 seconds. Here's the entire code to do that. + + + import PySimpleGUI as sg + import time + + # window that doesn't block + # Make a window, but don't use context manager + window = sg.Window('Running Timer', auto_size_text=True) + + # Create the layout + window_rows = [[sg.Text('Non-blocking GUI with updates')], + [sg.Text('', size=(8, 2), font=('Helvetica', 20), key='output') ], + [sg.Button('Quit')]] + # Layout the rows of the window and perform a read. Indicate the window is non-blocking! + window.Layout(window_rows).ReadNonBlocking() + + # + # Some place later in your code... + # You need to perform a ReadNonBlocking on your window every now and then or + # else it won't refresh + # + + for i in range(1, 1000): + window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i / 100), 60), i % 100)) + event, values = window.ReadNonBlocking() + if values is None or event == 'Quit': + break + time.sleep(.01) + else: + window.CloseNonBlocking() + + +What we have here is the same sequence of function calls as in the description. Get a window, add rows to it, show the window, and then refresh it every now and then. + +The new thing in this example is the call use of the Update method for the Text Element. The first thing we do inside the loop is "update" the text element that we made earlier. This changes the value of the text field on the window. The new value will be displayed when `window.ReadNonBlocking()` is called. if you want to have the window reflect your changes immediately, call `window.Refresh()`. + +Note the `else` statement on the for loop. This is needed because we're about to exit the loop while the window is still open. The user has not closed the window using the X nor a button so it's up to the caller to close the window using `CloseNonBlocking`. + + + +# Updating Elements (changing elements in active window) + +Persistent windows remain open and thus continue to interact with the user after the Read has returned. Often the program wishes to communicate results (output information) or change an Element's values (such as populating a List Element). + +You can use Update to do things like: +* Have one Element (appear to) make a change to another Element +* Disable a button, slider, input field, etc +* Change a button's text +* Change an Element's text or background color +* Add text to a scrolling output window +* Change the choices in a list +* etc + +The way this is done is via an Update method that is available for nearly all of the Elements. Here is an example of a program that uses a persistent window that is updated. + +![snap0272](https://user-images.githubusercontent.com/13696193/45260249-ec4e4000-b3af-11e8-853b-9b29d0bf7797.jpg) + + +In some programs these updates happen in response to another Element. This program takes a Spinner and a Slider's input values and uses them to resize a Text Element. The Spinner and Slider are on the left, the Text element being changed is on the right. + + + # Testing async window, see if can have a slider + # that adjusts the size of text displayed + + import PySimpleGUI as sg + fontSize = 12 + layout = [[sg.Spin([sz for sz in range(6, 172)], font=('Helvetica 20'), initial_value=fontSize, change_submits=True, key='spin'), + sg.Slider(range=(6,172), orientation='h', size=(10,20), + change_submits=True, key='slider', font=('Helvetica 20')), + sg.Text("Aa", size=(2, 1), font="Helvetica " + str(fontSize), key='text')]] + + sz = fontSize + window = sg.Window("Font size selector", grab_anywhere=False).Layout(layout) + # Event Loop + while True: + event, values= window.Read() + if event is None: + break + sz_spin = int(values['spin']) + sz_slider = int(values['slider']) + sz = sz_spin if sz_spin != fontSize else sz_slider + if sz != fontSize: + fontSize = sz + font = "Helvetica " + str(fontSize) + window.FindElement('text').Update(font=font) + window.FindElement('slider').Update(sz) + window.FindElement('spin').Update(sz) + + print("Done.") + + +Inside the event loop we read the value of the Spinner and the Slider using those Elements' keys. +For example, `values['slider']` is the value of the Slider Element. + +This program changes all 3 elements if either the Slider or the Spinner changes. This is done with these statements: + + window.FindElement('text').Update(font=font) + window.FindElement('slider').Update(sz) + window.FindElement('spin').Update(sz) + +Remember this design pattern because you will use it OFTEN if you use persistent windows. + +It works as follows. The call to `window.FindElement` returns the Element object represented by they provided `key`. This element is then updated by calling it's `Update` method. This is another example of Python's "chaining" feature. We could write this code using the long-form: + + text_element = window.FindElement('text') + text_element.Update(font=font) + +The takeaway from this exercise is that keys are key in PySimpleGUI's design. They are used to both read the values of the window and also to identify elements. As already mentioned, they are used as targets in Button calls. + + ### Updating Multiple Elements + If you have a large number of Elements to update, you can call `Window.UpdateElements()`. + +` UpdateElements(key_list, + value_list)` + +`key_list` - list of keys for elements you wish to update +`value_list` - list of values, one for each key + + window.UpdateElements(('name', 'address', 'phone'), ('Fred Flintstone', '123 Rock Quarry Road', '555#')) + + +# Keyboard & Mouse Capture +Beginning in version 2.10 you can capture keyboard key presses and mouse scroll-wheel events. Keyboard keys can be used, for example, to detect the page-up and page-down keys for a PDF viewer. To use this feature, there's a boolean setting in the Window call `return_keyboard_events` that is set to True in order to get keys returned along with buttons. + +Keys and scroll-wheel events are returned in exactly the same way as buttons. + +For scroll-wheel events, if the mouse is scrolled up, then the `button` text will be `MouseWheel:Up`. For downward scrolling, the text returned is `MouseWheel:Down` + +Keyboard keys return 2 types of key events. For "normal" keys (a,b,c, etc), a single character is returned that represents that key. Modifier and special keys are returned as a string with 2 parts: + + Key Sym:Key Code + +Key Sym is a string such as 'Control_L'. The Key Code is a numeric representation of that key. The left control key, when pressed will return the value 'Control_L:17' + + import PySimpleGUI as sg + + # Recipe for getting keys, one at a time as they are released + # If want to use the space bar, then be sure and disable the "default focus" + + with sg.Window("Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: + text_elem = sg.Text("", size=(18,1)) + layout = [[sg.Text("Press a key or scroll mouse")], + [text_elem], + [sg.Button("OK")]] + + window.Layout(layout) + # ---===--- Loop taking in user input --- # + while True: + event, value = window.ReadNonBlocking() + + if event == "OK" or (event is None and value is None): + print(event, "exiting") + break + if eventis not None: + text_elem.Update(event) + +You want to turn off the default focus so that there no buttons that will be selected should you press the spacebar. + +### Realtime Keyboard Capture +Use realtime keyboard capture by calling + + import PySimpleGUI as sg + + with sg.Window("Realtime Keyboard Test", return_keyboard_events=True, use_default_focus=False) as window: + layout = [[sg.Text("Hold down a key")], + [sg.Button("OK")]] + + window.Layout(layout) + + while True: + event, value = window.ReadNonBlocking() + + if event == "OK": + print(event, value, "exiting") + break + if eventis not None: + print(event) + elif value is None: + break + +# Menus + +Beginning in version 3.01 you can add a menubar to your window. You specify the menus in much the same way as you do window layouts, with lists. Menu selections are returned as button clicks, so be aware of your overall naming conventions. If you have an Exit button and also an Exit menu option, then you won't be able to tell the difference when your window.Read returns. Hopefully will not be a problem. + +This definition: + + menu_def = [['File', ['Open', 'Save', 'Exit',]], + ['Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], + ['Help', 'About...'],] + +Note the placement of ',' and of []. It's tricky to get the nested menus correct that implement cascading menus. See how paste has Special and Normal as a list after it. This means that Paste has a cascading menu with items Special and Normal. + +They menu_def layout produced this window: + +![menu](https://user-images.githubusercontent.com/13696193/45306723-56b7cb00-b4eb-11e8-8cbd-faef0c90f8b4.jpg) + + +You have used ALT-key in other Windows programs to navigate menus. For example Alt-F+X exits the program. The Alt-F pulls down the File menu. The X selects the entry marked Exit. + +The good news is that PySimpleGUI allows you to create the same kind of menus! Your program can play with the big-boys. And, it's trivial to do. + +All that's required is for your to add an "&" in front of the letter you want to appear with an underscore. When you hold the Alt key down you will see the menu with underlines that you marked. + +One other little bit of polish you can add are separators in your list. To add a line in your list of menu choices, create a menu entry that looks like this: ` '---'` + +This is an example Menu with underlines and a separator. + +``` +# ------ Menu Definition ------ # +menu_def = [['&File', ['&Open', '&Save', '---', 'Properties', 'E&xit' ]], + ['&Edit', ['Paste', ['Special', 'Normal',], 'Undo'],], + ['&Help', '&About...'],] +``` + And this is the spiffy menu it produced: + ![menus with shortcuts](https://user-images.githubusercontent.com/13696193/46251674-f5b74f00-c427-11e8-95c6-547adc59041b.jpg) + + + + +# Sample Applications + +Use the example programs as a starting basis for your GUI. Copy, paste, modify and run! The demo files are: + + | Source File| Description | +|--|--| +|**Demo_All_Widgets.py**| Nearly all of the Elements shown in a single window +|**Demo_Borderless_Window.py**| Create clean looking windows with no border +|**Demo_Button_States.py**| One way of implementing disabling of buttons +|**Demo_Calendar.py** | Demo of the Calendar Chooser button +|**Demo_Canvas.py** | window with a Canvas Element that is updated outside of the window +|**Demo_Chat.py** | A chat window with scrollable history +|**Demo_Chatterbot.py** | Front-end to Chatterbot Machine Learning project +|**Demo_Color.py** | How to interact with color using RGB hex values and named colors +|**Demo_Columns.py** | Using the Column Element to create more complex windows +|**Demo_Compare_Files.py** | Using a simple GUI front-end to create a compare 2-files utility +|**Demo_Cookbook_Browser.py** | Source code browser for all Recipes in Cookbook +|**Demo_Dictionary.py** | Specifying and using return values in dictionary format +**Demo_DOC_Viewer_PIL.py** | Display a PDF, HTML, ebook file, etc in your window +|**Demo_DisplayHash1and256.py** | Using high level API and custom window to implement a simple display hash code utility +|**Demo_DuplicateFileFinder.py** | High level API used to get a folder that is used by utility that finds duplicate files. Uses progress meter to show progress. 2 lines of code required to add GUI and meter +|**Demo_Fill_Form.py** | How to perform a bulk-fill for a window. Saving and loading a window from disk +|**Demo Font Sizer.py** | Demonstrates Elements updating other Elements +|**Demo_Func_Callback_Simulator.py** | For the Raspberry Pi crowd. Event loop that simulates traditional GUI callback functions should you already have an architecture that uses them +|**Demo_GoodColors.py** | Using some of the pre-defined PySimpleGUI individual colors +|**Demo_HowDoI.py** | This is a utility to be experienced! It will change how you code +|**Demo_Img_Viewer.py** | Display jpg, png,tiff, bmp files +|**Demo_Keyboard.py** | Using blocking keyboard events +|**Demo_Keyboard_Realtime.py** | Using non-blocking / realtime keyboard events +|**Demo_Machine_Learning.py** | A sample Machine Learning front end +|**Demo_Matplotlib.py** | Integrating with Matplotlib to create a single graph +|**Demo_Matplotlib_Animated.py** | Animated Matplotlib line graph +|**Demo_Matplotlib_Animated_Scatter.py** | Animated Matplotlib scatter graph +|**Demo_Matplotlib_Browser.py** | Browse Matplotlib gallery +|**Demo_Media_Player.py** | Non-blocking window with a media player layout. Demonstrates button graphics, Update method +|**Demo_MIDI_Player.py** | GUI wrapper for Mido MIDI package. Functional MIDI player that controls attached MIDI devices +|**Demo_NonBlocking_Form.py** | a basic async window +|**Demo_OpenCV.py** | Integrated with OpenCV +|**Demo_Password_Login** | Password protection using SHA1 +|**Demo_PDF_Viewer.py** | Submitted by a user! Previews PDF documents. Uses keyboard input & mouse scrollwheel to navigate +|**Demo_Pi_LEDs.py** | Control GPIO using buttons +|**Demo_Pi_Robotics.py** | Simulated robot control using realtime buttons +|**Demo_PNG_Vierwer.py** | Uses Image Element to display PNG files +| **Demo_Progress_Meters.py** | Demonstrates using 2 progress meters simultaneously +|**Demo_Recipes.py** | A collection of various Recipes. Note these are not the same as the Recipes in the Recipe Cookbook +|**Demo_Script_Launcher.py** | Demonstrates one way of adding a front-end onto several command line scripts +|**Demo_Script_Parameters.py** | Add a 1-line GUI to the front of your previously command-line only scripts +|**Demo_Tabbed_Form.py** | Using the Tab feature +|**Demo_Table_Simulation.py** | Use input fields to display and edit tables +|**Demo_Timer.py** | Simple non-blocking window + +## Packages Used In Demos + + + While the core PySimpleGUI code does not utilize any 3rd party packages, some of the demos do. They add a GUI to a few popular packages. These packages include: + * [Chatterbot](https://github.com/gunthercox/ChatterBot) + * [Mido](https://github.com/olemb/mido) + * [Matplotlib](https://matplotlib.org/) + * [PyMuPDF](https://github.com/rk700/PyMuPDF) + + +# Creating a Windows .EXE File + +It's possible to create a single .EXE file that can be distributed to Windows users. There is no requirement to install the Python interpreter on the PC you wish to run it on. Everything it needs is in the one EXE file, assuming you're running a somewhat up to date version of Windows. + +Installation of the packages, you'll need to install PySimpleGUI and PyInstaller (you need to install only once) + +``` +pip install PySimpleGUI +pip install PyInstaller + +``` + +To create your EXE file from your program that uses PySimpleGUI, `my_program.py`, enter this command in your Windows command prompt: + +``` +pyinstaller -wF my_program.py + +``` + +You will be left with a single file, `my_program.exe`, located in a folder named `dist` under the folder where you executed the `pyinstaller` command. + +That's all... Run your `my_program.exe` file on the Windows machine of your choosing. + +> "It's just that easy." + +(famous last words that screw up just about anything being referenced) + +Your EXE file should run without creating a "shell window". Only the GUI window should show up on your taskbar. + +If you get a crash with something like: +``` +ValueError: script '.......\src\tkinter' not found +``` + +Then try adding **`--hidden-import tkinter`** to your command + + + +## Fun Stuff +Here are some things to try if you're bored or want to further customize + +**Debug Output** +Be sure and check out the EasyPrint (Print) function described in the high-level API section. Leave your code the way it is, route your stdout and stderror to a scrolling window. + +For a fun time, add these lines to the top of your script + + import PySimpleGUI as sg + print = sg.Print + +This will turn all of your print statements into prints that display in a window on your screen rather than to the terminal. + +**Look and Feel** +Dial in the look and feel that you like with the `SetOptions` function. You can change all of the defaults in one function call. One line of code to customize the entire GUI. +Or beginning in version 2.9 you can choose from a look and feel using pre-defined color schemes. Call ChangeLookAndFeel with a description string. + + sg.ChangeLookAndFeel('GreenTan') + +Valid values for the description string are: + + GreenTan + LightGreen + BluePurple + Purple + BlueMono + GreenMono + BrownBlue + BrightColors + NeutralBlue + Kayak + SandyBeach + TealMono + +To see the latest list of color choices, take a look at the bottom of the `PySimpleGUI.py` file where you'll find the `ChangLookAndFeel` function. + +You can also combine the `ChangeLookAndFeel` function with the `SetOptions` function to quickly modify one of the canned color schemes. Maybe you like the colors but was more depth to your bezels. You can dial in exactly what you want. + +**ObjToString** +Ever wanted to easily display an objects contents easily? Use ObjToString to get a nicely formatted recursive walk of your objects. +This statement: + + print(sg.ObjToSting(x)) + +And this was the output + + + abc = abc + attr12 = 12 + c = + b = + a = + attr1 = 1 + attr2 = 2 + attr3 = three + attr10 = 10 + attrx = x + +You'll quickly wonder how you ever coded without it. + +--- +# Known Issues +While not an "issue" this is a ***stern warning*** + +## **Do not attempt** to call `PySimpleGUI` from multiple threads! It's `tkinter` based and `tkinter` has issues with multiple threads + +**Progress Meters** - the visual graphic portion of the meter may be off. May return to the native tkinter progress meter solution in the future. Right now a "custom" progress meter is used. On the bright side, the statistics shown are extremely accurate and can tell you something about the performance of your code. If you are running 2 or more progress meters at the same time using `OneLineProgressMeter`, you need to close the meter by using the "Cancel" button rather than the X + +**Async windows** - these include the 'easy' windows (`OneLineProgressMeter` and EasyPrint/Print). If you start overlapping having Async windows open with normal windows then things get a littler squirrelly. Still tracking down the issues and am making it more solid every day possible. You'll know there's an issue when you see blank window. + +**EasyPrint** - EasyPrint is a new feature that's pretty awesome. You print and the output goes to a window, with a scroll bar, that you can copy and paste from. Being a new feature, it's got some potential problems. There are known interaction problems with other GUI windows. For example, closing a Print window can also close other windows you have open. For now, don't close your debug print window until other windows are closed too. + +## Contributing + +A MikeTheWatchGuy production... entirely responsible for this code.... unless it causes you trouble in which case I'm not at all responsible. + +## Versions +|Version | Description | +|--|--| +| 1.0.9 | July 10, 2018 - Initial Release | +| 1.0.21 | July 13, 2018 - Readme updates | +| 2.0.0 | July 16, 2018 - ALL optional parameters renamed from CamelCase to all_lower_case +| 2.1.1 | July 18, 2018 - Global settings exposed, fixes +| 2.2.0| July 20, 2018 - Image Elements, Print output +| 2.3.0 | July 23, 2018 - Changed form.Read return codes, Slider Elements, Listbox element. Renamed some methods but left legacy calls in place for now. +| 2.4.0 | July 24, 2018 - Button images. Fixes so can run on Raspberry Pi +| 2.5.0 | July 26, 2018 - Colors. Listbox scrollbar. tkinter Progress Bar instead of homegrown. +| 2.6.0 | July 27, 2018 - auto_size_button setting. License changed to LGPL 3+ +| 2.7.0 | July 30, 2018 - realtime buttons, window_location default setting +| 2.8.0 | Aug 9, 2018 - New None default option for Checkbox element, text color option for all elements, return values as a dictionary, setting focus, binding return key +| 2.9.0 | Aug 16,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, colored text defaults +| 2.10.0 | Aug 25, 2018 - Keyboard & Mouse features (Return individual keys as if buttons, return mouse scroll-wheel as button, bind return-key to button, control over keyboard focus), SaveAs Button, Update & Get methods for InputText, Update for Listbox, Update & Get for Checkbox, Get for Multiline, Color options for Text Element Update, Progess bar Update can change max value, Update for Button to change text & colors, Update for Image Element, Update for Slider, Form level text justification, Turn off default focus, scroll bar for Listboxes, Images can be from filename or from in-RAM, Update for Image). Fixes - text wrapping in buttons, msg box, removed slider borders entirely and others +| 2.11.0 | Aug 29, 2018 - Lots of little changes that are needed for the demo programs to work. Buttons have their own default element size, fix for Mac default button color, padding support for all elements, option to immediately return if list box gets selected, FilesBrowse button, Canvas Element, Frame Element, Slider resolution option, Form.Refresh method, better text wrapping, 'SystemDefault' look and feel settin +| 2.20.0 | Sept 4, 2018 - Some sizable features this time around of interest to advanced users. Renaming of the MsgBox functions to Popup. Renaming GetFile, etc, to PopupGetFile. High-level windowing capabilities start with Popup, PopupNoWait/PopupNonblocking, PopupNoButtons, default icon, change_submits option for Listbox/Combobox/Slider/Spin/, New OptionMenu element, updating elements after shown, system defaul color option for progress bars, new button type (Dummy Button) that only closes a window, SCROLLABLE Columns!! (yea, playing in the Big League now), LayoutAndShow function removed, form.Fill - bulk updates to forms, FindElement - find element based on key value (ALL elements have keys now), no longer use grid packing for row elements (a potentially huge change), scrolled text box sizing changed, new look and feel themes (Dark, Dark2, Black, Tan, TanBlue, DarkTanBlue, DarkAmber, DarkBlue, Reds, Green) +| 2.30.0 | Sept 6, 2018 - Calendar Chooser (button), borderless windows, load/save form to disk +| 3.0.0 | Sept 7, 2018 - The "fix for poor choice of 2.x numbers" release. Color Chooser (button), "grab anywhere" windows are on by default, disable combo boxes, Input Element text justification (last part needed for 'tables'), Image Element changes to support OpenCV?, PopupGetFile and PopupGetFolder have better no_window option +| 3.01.01 | Sept 10, 2018 - Menus! (sort of a big deal) +| 3.01.02 | Step 11, 2018 - All Element.Update functions have a `disabled` parameter so they can be disabled. Renamed some parameters in Update function (sorry if I broke your code), fix for bug in Image.Update. Wasn't setting size correctly, changed grab_anywhere logic again,added grab anywhere option to PupupGetText (assumes disabled) +| 3.02.00 | Sept 14, 2018 - New Table Element (Beta release), MsgBox removed entirely, font setting for InputText Element, **packing change** risky change that allows some Elements to be resized,removed command parameter from Menu Element, new function names for ReadNonBlocking (Finalize, PreRead), change to text element autosizing and wrapping (yet again), lots of parameter additions to Popup functions (colors, etc). +| 3.03.00 | New feature - One Line Progress Meters, new display_row_numbers for Table Element, fixed bug in EasyProgresssMeters (function will soon go away), OneLine and Easy progress meters set to grab anywhere but can be turned off. +| 03,04.00 | Sept 18, 2018 - New features - Graph Element, Frame Element, more settings exposed to Popup calls. See notes below for more. +| 03.04.01 | Sept 18, 2018 - See release notes +| 03.05.00 | Sept 20, 2018 - See release notes +| 03.05.01 | Sept 22, 2018 - See release notes +| 03.05.02 | Sept 23, 2018 - See release notes +| 03.06.00 | Sept 23, 2018 - Goodbye FlexForm, hello Window +| 03.08.00 | Sept 25, 2018 - Tab and TabGroup Elements\ +| 01.00.00 for 2.7 | Sept 25, 2018 - First release for 2.7 +| 03.08.04 | Sept 30, 2018 - See release notes | 03.09.00 | Oct 1, 2018 | | 2.7 01.01.00 | Oct 1, 2018 | 2.7 01.01.02 | Oct 8, 2018 | 03.09.01 | Oct 8, 2018 | 3.9.3 & 1.1.3 | Oct 11, 2018 - - -## Release Notes -2.3 - Sliders, Listbox's and Image elements (oh my!) - -If using Progress Meters, avoid cancelling them when you have another window open. It could lead to future windows being blank. It's being worked on. - -New debug printing capability. `sg.Print` - -2.5 Discovered issue with scroll bar on `Output` elements. The bar will match size of ROW not the size of the element. Normally you never notice this due to where on a form the `Output` element goes. - -Listboxes are still without scrollwheels. The mouse can drag to see more items. The mouse scrollwheel will also scroll the list and will `page up` and `page down` keys. - -2.7 Is the "feature complete" release. Pretty much all features are done and in the code - -2.8 More text color controls. The caller has more control over things like the focus and what buttons should be clicked when enter key is pressed. Return values as a dictionary! (NICE addition) - -2.9 COLUMNS! This is the biggest feature and had the biggest impact on the code base. It was a difficult feature to add, but it was worth it. Can now make even more layouts. Almost any layout is possible with this addition. - -.................. insert releases 2.9 to 2.30 ................. - -3.0 We've come a long way baby! Time for a major revision bump. One reason is that the numbers started to confuse people the latest release was 2.30, but some people read it as 2.3 and thought it went backwards. I kinda messed up the 2.x series of numbers, so why not start with a clean slate. A lot has happened anyway so it's well earned. - -One change that will set PySimpleGUI apart is the parlor trick of being able to move the window by clicking on it anywhere. This is turned on by default. It's not a common way to interact with windows. Normally you have to move using the titlebar. Not so with PySimpleGUI. Now you can drag using any part of the window. You will want to turn this off for windows with sliders. This feature is enabled in the Window call. - -Related to the Grab Anywhere feature is the no_titlebar option, again found in the call to Window. Your window will be a spiffy, borderless window. It's a really interesting effect. Slight problem is that you do not have an icon on the taskbar with these types of windows, so if you don't supply a button to close the window, there's no way to close it other than task manager. - -3.0.2 Still making changes to Update methods with many more ahead in the future. Continue to mess with grab anywhere option. Needed to disable in more places such as the PopupGetText function. Any time these is text input on a form, you generally want to turn off the grab anywhere feature. - -#### 3.2.0 - Biggest change was the addition of the Table Element. Trying to make changes so that form resizing is a possibility but unknown if will work in the long run. Removed all MsgBox, Get* functions and replaced with Popup functions. Popups had multiple new parameters added to change the look and feel of a popup. - -#### 3.3.0 -OneLineProgressMeter function added which gives you not only a one-line solution to progress meters, but it also gives you the ability to have more than 1 running at the same time, something not possible with the EasyProgressMeterCall - -#### 3.4.0 - -* Frame - New Element - a labelled frame for grouping elements. Similar - to Column -* Graph (like a Canvas element except uses the caller's - coordinate system rather than tkinter's). -* initial_folder - sets starting folder for browsing type buttons (browse for file/folder). -* Buttons return key value rather than button text **If** a `key` is specified, -* - OneLineProgressMeter! Replaced EasyProgressMeter (sorry folks that's - the way progress works sometimes) - * Popup - changed ALL of the Popup calls to provide many more customization settings - * Popup - * PopupGetFolder - * PopupGetFile - * PopupGetText - * Popup - * PopupNoButtons - * PopupNonBlocking - * PopupNoTitlebar - * PopupAutoClose - * PopupCancel - * PopupOK - * PopupOKCancel - * PopupYesNo - -#### 3.4.1 -* Button.GetText - Button class method. Returns the current text being shown on a button. -* Menu - Tearoff option. Determines if menus should allow them to be torn off -* Help - Shorcut button. Like Submit, cancel, etc -* ReadButton - shortcut for ReadFormButton - -#### 3.5.0 -* Tool Tips for all elements -* Clickable text -* Text Element relief setting -* Keys as targets for buttons -* New names for buttons: - * Button = SimpleButton - * RButton = ReadButton = ReadFormButton -* Double clickable list entries -* Auto sizing table widths works now -* Feature DELETED - Scaling. Removed from all elements - -#### 3.5.1 -* Bug fix for broken PySimpleGUI if Python version < 3.6 (sorry!) -* LOTS of Readme changes - -#### 3.5.2 -* Made `Finalize()` in a way that it can be chained -* Fixed bug in return values from Frame Element contents - -#### 3.6.0 -* Renamed FlexForm to Window -* Removed LookAndFeel capability from Mac platform. - -#### 3.8.0 -* Tab and TabGroup Elements - awesome new capabilities - -#### 1.0.0 Python 2.7 -It's official. There is a 2.7 version of PySimpleGUI! - -#### 3.8.2 -* Exposed `TKOut` in Output Element -* `DrawText` added to Graph Elements -* Removed `Window.UpdateElements` -* `Window.grab_anywere` defaults to False - -#### 3.8.3 -* Listbox, Slider, Combobox, Checkbox, Spin, Tab Group - if change_submits is set, will return the Element's key rather than '' -* Added change_submits capability to Checkbox, Tab Group -* Combobox - Can set value to an Index into the Values table rather than the Value itself -* Warnings added to Drawing routines for Graph element (rather than crashing) -* Window - can "force top level" window to be used rather than a normal window. Means that instead of calling Tk to get a window, will call TopLevel to get the window -* Window Disable / Enable - Disables events (button clicks, etc) for a Window. Use this when you open a second window and want to disable the first window from doing anything. This will simulate a 'dialog box' -* Tab Group returns a value with Window is Read. Return value is the string of the selected tab -* Turned off grab_anywhere for Popups -* New parameter, default_extension, for PopupGetFile -* Keyboard shortcuts for menu items. Can hold ALT key to select items in men -* Removed old-style Tabs - Risky change because it hit fundamental window packing and creation. Will also break any old code using this style tab (sorry folks this is how progress happens) - -#### 3.8.6 - -* Fix for Menus. -* Fixed table colors. Now they work -* Fixed returning keys for tabs -* Window Hide / UnHide methods -* Changed all Popups to remove context manager -* Error checking for Graphing objects and for Element Updates + + +## Release Notes +2.3 - Sliders, Listbox's and Image elements (oh my!) + +If using Progress Meters, avoid cancelling them when you have another window open. It could lead to future windows being blank. It's being worked on. + +New debug printing capability. `sg.Print` + +2.5 Discovered issue with scroll bar on `Output` elements. The bar will match size of ROW not the size of the element. Normally you never notice this due to where on a form the `Output` element goes. + +Listboxes are still without scrollwheels. The mouse can drag to see more items. The mouse scrollwheel will also scroll the list and will `page up` and `page down` keys. + +2.7 Is the "feature complete" release. Pretty much all features are done and in the code + +2.8 More text color controls. The caller has more control over things like the focus and what buttons should be clicked when enter key is pressed. Return values as a dictionary! (NICE addition) + +2.9 COLUMNS! This is the biggest feature and had the biggest impact on the code base. It was a difficult feature to add, but it was worth it. Can now make even more layouts. Almost any layout is possible with this addition. + +.................. insert releases 2.9 to 2.30 ................. + +3.0 We've come a long way baby! Time for a major revision bump. One reason is that the numbers started to confuse people the latest release was 2.30, but some people read it as 2.3 and thought it went backwards. I kinda messed up the 2.x series of numbers, so why not start with a clean slate. A lot has happened anyway so it's well earned. + +One change that will set PySimpleGUI apart is the parlor trick of being able to move the window by clicking on it anywhere. This is turned on by default. It's not a common way to interact with windows. Normally you have to move using the titlebar. Not so with PySimpleGUI. Now you can drag using any part of the window. You will want to turn this off for windows with sliders. This feature is enabled in the Window call. + +Related to the Grab Anywhere feature is the no_titlebar option, again found in the call to Window. Your window will be a spiffy, borderless window. It's a really interesting effect. Slight problem is that you do not have an icon on the taskbar with these types of windows, so if you don't supply a button to close the window, there's no way to close it other than task manager. + +3.0.2 Still making changes to Update methods with many more ahead in the future. Continue to mess with grab anywhere option. Needed to disable in more places such as the PopupGetText function. Any time these is text input on a form, you generally want to turn off the grab anywhere feature. + +#### 3.2.0 + Biggest change was the addition of the Table Element. Trying to make changes so that form resizing is a possibility but unknown if will work in the long run. Removed all MsgBox, Get* functions and replaced with Popup functions. Popups had multiple new parameters added to change the look and feel of a popup. + +#### 3.3.0 +OneLineProgressMeter function added which gives you not only a one-line solution to progress meters, but it also gives you the ability to have more than 1 running at the same time, something not possible with the EasyProgressMeterCall + +#### 3.4.0 + +* Frame - New Element - a labelled frame for grouping elements. Similar + to Column +* Graph (like a Canvas element except uses the caller's + coordinate system rather than tkinter's). +* initial_folder - sets starting folder for browsing type buttons (browse for file/folder). +* Buttons return key value rather than button text **If** a `key` is specified, +* + OneLineProgressMeter! Replaced EasyProgressMeter (sorry folks that's + the way progress works sometimes) + * Popup - changed ALL of the Popup calls to provide many more customization settings + * Popup + * PopupGetFolder + * PopupGetFile + * PopupGetText + * Popup + * PopupNoButtons + * PopupNonBlocking + * PopupNoTitlebar + * PopupAutoClose + * PopupCancel + * PopupOK + * PopupOKCancel + * PopupYesNo + +#### 3.4.1 +* Button.GetText - Button class method. Returns the current text being shown on a button. +* Menu - Tearoff option. Determines if menus should allow them to be torn off +* Help - Shorcut button. Like Submit, cancel, etc +* ReadButton - shortcut for ReadFormButton + +#### 3.5.0 +* Tool Tips for all elements +* Clickable text +* Text Element relief setting +* Keys as targets for buttons +* New names for buttons: + * Button = SimpleButton + * RButton = ReadButton = ReadFormButton +* Double clickable list entries +* Auto sizing table widths works now +* Feature DELETED - Scaling. Removed from all elements + +#### 3.5.1 +* Bug fix for broken PySimpleGUI if Python version < 3.6 (sorry!) +* LOTS of Readme changes + +#### 3.5.2 +* Made `Finalize()` in a way that it can be chained +* Fixed bug in return values from Frame Element contents + +#### 3.6.0 +* Renamed FlexForm to Window +* Removed LookAndFeel capability from Mac platform. + +#### 3.8.0 +* Tab and TabGroup Elements - awesome new capabilities + +#### 1.0.0 Python 2.7 +It's official. There is a 2.7 version of PySimpleGUI! + +#### 3.8.2 +* Exposed `TKOut` in Output Element +* `DrawText` added to Graph Elements +* Removed `Window.UpdateElements` +* `Window.grab_anywere` defaults to False + +#### 3.8.3 +* Listbox, Slider, Combobox, Checkbox, Spin, Tab Group - if change_submits is set, will return the Element's key rather than '' +* Added change_submits capability to Checkbox, Tab Group +* Combobox - Can set value to an Index into the Values table rather than the Value itself +* Warnings added to Drawing routines for Graph element (rather than crashing) +* Window - can "force top level" window to be used rather than a normal window. Means that instead of calling Tk to get a window, will call TopLevel to get the window +* Window Disable / Enable - Disables events (button clicks, etc) for a Window. Use this when you open a second window and want to disable the first window from doing anything. This will simulate a 'dialog box' +* Tab Group returns a value with Window is Read. Return value is the string of the selected tab +* Turned off grab_anywhere for Popups +* New parameter, default_extension, for PopupGetFile +* Keyboard shortcuts for menu items. Can hold ALT key to select items in men +* Removed old-style Tabs - Risky change because it hit fundamental window packing and creation. Will also break any old code using this style tab (sorry folks this is how progress happens) + +#### 3.8.6 + +* Fix for Menus. +* Fixed table colors. Now they work +* Fixed returning keys for tabs +* Window Hide / UnHide methods +* Changed all Popups to remove context manager +* Error checking for Graphing objects and for Element Updates ### 3.9.0 & 1.1.0 * The FIRST UNIFIED version of the code! @@ -3436,10 +3488,10 @@ It's official. There is a 2.7 version of PySimpleGUI! ### 3.9.1 & 1.1.2 * Tab features - * Themes - * Enable / Disable - * Tab text colors - * Selected tab color + * Themes + * Enable / Disable + * Tab text colors + * Selected tab color * New GetListValues method for Listbox * Can now have multiple progress bars in 1 window * Fix for closing debug-output window with other windows open @@ -3473,97 +3525,97 @@ It's official. There is a 2.7 version of PySimpleGUI! * Exposed look and feel table -### Upcoming -Make suggestions people! Future release features - -Port to other graphic engines. Hook up the front-end interface to a backend other than tkinter. Qt, WxPython, etc. WxPython is higher priority. - - - -## Code Condition - - Make it run - Make it right - Make it fast - -It's a recipe for success if done right. PySimpleGUI has completed the "Make it run" phase. It's far from "right" in many ways. These are being worked on. The module is particularly poor for PEP 8 compliance. It was a learning exercise that turned into a somewhat complete GUI solution for lightweight problems. - -While the internals to PySimpleGUI are a tad sketchy, the public interfaces into the SDK are more strictly defined and comply with PEP 8 for the most part. - -Please log bugs and suggestions in the GitHub! It will only make the code stronger and better in the end, a good thing for us all, right? - -## Design - -A moment about the design-spirit of `PySimpleGUI`. From the beginning, this package was meant to take advantage of Python's capabilities with the goal of programming ease. - -**Single File** -While not the best programming practice, the implementation resulted in a single file solution. Only one file is needed, PySimpleGUI.py. You can post this file, email it, and easily import it using one statement. - -**Functions as objects** -In Python, functions behave just like object. When you're placing a Text Element into your form, you may be sometimes calling a function and other times declaring an object. If you use the word Text, then you're getting an object. If you're using `Txt`, then you're calling a function that returns a `Text` object. - -**Lists** -It seemed quite natural to use Python's powerful list constructs when possible. The form is specified as a series of lists. Each "row" of the GUI is represented as a list of Elements. When the form read returns the results to the user, all of the results are presented as a single list. This makes reading a form's values super-simple to do in a single line of Python code. - -**Dictionaries** -Want to view your form's results as a dictionary instead of a list... no problem, just use the `key` keyword on your elements. For complex forms with a lot of values that need to be changed frequently, this is by far the best way of consuming the results. - -You can also look up elements using their keys. This is an excellent way to update elements in reaction to another element. Call `form.FindElement(key)` to get the Element. +### Upcoming +Make suggestions people! Future release features + +Port to other graphic engines. Hook up the front-end interface to a backend other than tkinter. Qt, WxPython, etc. WxPython is higher priority. + + + +## Code Condition + + Make it run + Make it right + Make it fast + +It's a recipe for success if done right. PySimpleGUI has completed the "Make it run" phase. It's far from "right" in many ways. These are being worked on. The module is particularly poor for PEP 8 compliance. It was a learning exercise that turned into a somewhat complete GUI solution for lightweight problems. + +While the internals to PySimpleGUI are a tad sketchy, the public interfaces into the SDK are more strictly defined and comply with PEP 8 for the most part. + +Please log bugs and suggestions in the GitHub! It will only make the code stronger and better in the end, a good thing for us all, right? + +## Design + +A moment about the design-spirit of `PySimpleGUI`. From the beginning, this package was meant to take advantage of Python's capabilities with the goal of programming ease. + +**Single File** +While not the best programming practice, the implementation resulted in a single file solution. Only one file is needed, PySimpleGUI.py. You can post this file, email it, and easily import it using one statement. + +**Functions as objects** +In Python, functions behave just like object. When you're placing a Text Element into your form, you may be sometimes calling a function and other times declaring an object. If you use the word Text, then you're getting an object. If you're using `Txt`, then you're calling a function that returns a `Text` object. + +**Lists** +It seemed quite natural to use Python's powerful list constructs when possible. The form is specified as a series of lists. Each "row" of the GUI is represented as a list of Elements. When the form read returns the results to the user, all of the results are presented as a single list. This makes reading a form's values super-simple to do in a single line of Python code. + +**Dictionaries** +Want to view your form's results as a dictionary instead of a list... no problem, just use the `key` keyword on your elements. For complex forms with a lot of values that need to be changed frequently, this is by far the best way of consuming the results. + +You can also look up elements using their keys. This is an excellent way to update elements in reaction to another element. Call `form.FindElement(key)` to get the Element. **Named / Optional Parameters** This is a language feature that is featured **heavily** in all of the API calls, both functions and classes. Elements are configured, in-place, by setting one or more optional parameters. For example, a Text element's color is chosen by setting the optional `text_color` parameter. - -## Author -MikeTheWatchGuy - -## Demo Code Contributors - - [JorjMcKie](https://github.com/JorjMcKie) - PDF and image viewers (plus a number of code suggestions) -[Otherion](https://github.com/Otherion) - Table Demos Panda & CSV. Loads of suggestions to the core APIs - -## License - -GNU Lesser General Public License (LGPL 3) + - -## Acknowledgments - -* [JorjMcKie](https://github.com/JorjMcKie) was the motivator behind the entire project. His wxsimpleGUI concepts sparked PySimpleGUI into existence -* [Fredrik Lundh](https://wiki.python.org/moin/FredrikLundh) for his work on `tkinter` -* [Ruud van der Ham](https://forum.pythonistacafe.com/u/Ruud) for all the help he's provided as a Python-mentor. Quite a few tricky bits of logic was supplied by Ruud. The dual-purpose return values scheme is Ruud's for example -* **Numerous** users who provided feature suggestions! Many of the cool features were suggested by others. If you were one of them and are willing to take more credit, I'll list you here if you give me permission. Most are too modest -* [moshekaplan](https://github.com/moshekaplan)/**[tkinter_components](https://github.com/moshekaplan/tkinter_components)** wrote the code for the Calendar Chooser Element. It was lifted straight from GitHub -* [Bryan Oakley](https://stackoverflow.com/users/7432/bryan-oakley) for the code that enables the `grab_anywhere` feature. -* [Otherion](https://github.com/Otherion) for help with Tables, being a sounding board for new features, naming functions, ..., all around great help -* [agjunyent](https://github.com/agjunyent) figured out how to properly make tabs and wrote prototype code that demonstrated how to do it -* [jfongattw](https://github.com/jfongattw) huge suggestion... dictionaries. turned out to be -* one of the most critical constructs in PySimpleGUI -* [venim](https://github.com/venim) code to doing Alt-Selections in menus, updating Combobox using index, request to disable windows (a really good idea), checkbox and tab submits on change, returning keys for elements that have change_submits set, ... + +## Author +MikeTheWatchGuy + +## Demo Code Contributors + + [JorjMcKie](https://github.com/JorjMcKie) - PDF and image viewers (plus a number of code suggestions) +[Otherion](https://github.com/Otherion) - Table Demos Panda & CSV. Loads of suggestions to the core APIs + +## License + +GNU Lesser General Public License (LGPL 3) + + +## Acknowledgments + +* [JorjMcKie](https://github.com/JorjMcKie) was the motivator behind the entire project. His wxsimpleGUI concepts sparked PySimpleGUI into existence +* [Fredrik Lundh](https://wiki.python.org/moin/FredrikLundh) for his work on `tkinter` +* [Ruud van der Ham](https://forum.pythonistacafe.com/u/Ruud) for all the help he's provided as a Python-mentor. Quite a few tricky bits of logic was supplied by Ruud. The dual-purpose return values scheme is Ruud's for example +* **Numerous** users who provided feature suggestions! Many of the cool features were suggested by others. If you were one of them and are willing to take more credit, I'll list you here if you give me permission. Most are too modest +* [moshekaplan](https://github.com/moshekaplan)/**[tkinter_components](https://github.com/moshekaplan/tkinter_components)** wrote the code for the Calendar Chooser Element. It was lifted straight from GitHub +* [Bryan Oakley](https://stackoverflow.com/users/7432/bryan-oakley) for the code that enables the `grab_anywhere` feature. +* [Otherion](https://github.com/Otherion) for help with Tables, being a sounding board for new features, naming functions, ..., all around great help +* [agjunyent](https://github.com/agjunyent) figured out how to properly make tabs and wrote prototype code that demonstrated how to do it +* [jfongattw](https://github.com/jfongattw) huge suggestion... dictionaries. turned out to be +* one of the most critical constructs in PySimpleGUI +* [venim](https://github.com/venim) code to doing Alt-Selections in menus, updating Combobox using index, request to disable windows (a really good idea), checkbox and tab submits on change, returning keys for elements that have change_submits set, ... * [rtrrtr](https://github.com/rtrrtr) Helped get the 2.7 and 3.x code unified (big damned deal) * Tony Crewe (anthony.crewe@gmail.com) Generously provided his classroom materials that he has written to teach a GUI course. If you're an educator and want to trade materials with Tony, he would like to hear from you. - -## How Do I -Finally, I must thank the fine folks at How Do I. -https://github.com/gleitz/howdoi -Their utility has forever changed the way and pace in which I can program. I urge you to try the HowDoI.py application here on GitHub. Trust me, **it's going to be worth the effort!** -Here are the steps to run that application - - Install howdoi: - pip install howdoi - Test your install: - python -m howdoi howdoi.py - To run it: - Python HowDoI.py - -The pip command is all there is to the setup. - -The way HowDoI works is that it uses your search term to look through stack overflow posts. It finds the best answer, gets the code from the answer, and presents it as a response. It gives you the correct answer OFTEN. It's a miracle that it work SO well. -For Python questions, I simply start my query with 'Python'. Let's say you forgot how to reverse a list in Python. When you run HowDoI and ask this question, this is what you'll see. - -![howdoiwithhistory](https://user-images.githubusercontent.com/13696193/45064009-5fd61180-b07f-11e8-8ead-eb0d1ff3a6be.jpg) - - - -In the hands of a competent programmer, this tool is **amazing**. It's a must-try kind of program that has completely changed my programming process. I'm not afraid of asking for help! You just have to be smart about using what you find. - -The PySimpleGUI window that the results are shown in is an 'input' field which means you can copy and paste the results right into your code. + +## How Do I +Finally, I must thank the fine folks at How Do I. +https://github.com/gleitz/howdoi +Their utility has forever changed the way and pace in which I can program. I urge you to try the HowDoI.py application here on GitHub. Trust me, **it's going to be worth the effort!** +Here are the steps to run that application + + Install howdoi: + pip install howdoi + Test your install: + python -m howdoi howdoi.py + To run it: + Python HowDoI.py + +The pip command is all there is to the setup. + +The way HowDoI works is that it uses your search term to look through stack overflow posts. It finds the best answer, gets the code from the answer, and presents it as a response. It gives you the correct answer OFTEN. It's a miracle that it work SO well. +For Python questions, I simply start my query with 'Python'. Let's say you forgot how to reverse a list in Python. When you run HowDoI and ask this question, this is what you'll see. + +![howdoiwithhistory](https://user-images.githubusercontent.com/13696193/45064009-5fd61180-b07f-11e8-8ead-eb0d1ff3a6be.jpg) + + + +In the hands of a competent programmer, this tool is **amazing**. It's a must-try kind of program that has completely changed my programming process. I'm not afraid of asking for help! You just have to be smart about using what you find. + +The PySimpleGUI window that the results are shown in is an 'input' field which means you can copy and paste the results right into your code. \ No newline at end of file