From f3776ff5d4b3dbd5e265b02f29fed5d77887199d Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Thu, 25 Oct 2018 19:34:11 -0400 Subject: [PATCH 1/6] Multi-window support (experimental release), BringToFront method --- PySimpleGUI.py | 91 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 18 deletions(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index b299e63e..b0308f53 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -178,6 +178,9 @@ ThisRow = 555666777 # magic number # DEFAULT_WINDOW_ICON = '' MESSAGE_BOX_LINE_WIDTH = 60 +# Key representing a Read timeout +TIMEOUT_KEY = '__timeout__' + # 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(): @@ -380,7 +383,8 @@ class Element(): else: self.ParentForm.LastButtonClicked = self.DisplayText self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop def ReturnKeyHandler(self, event): MyForm = self.ParentForm @@ -397,7 +401,8 @@ class Element(): else: self.ParentForm.LastButtonClicked = '' self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop def ComboboxSelectHandler(self, event): MyForm = self.ParentForm @@ -408,7 +413,8 @@ class Element(): else: self.ParentForm.LastButtonClicked = '' self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop def CheckboxHandler(self): MyForm = self.ParentForm @@ -417,7 +423,8 @@ class Element(): else: self.ParentForm.LastButtonClicked = '' self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() def TabGroupSelectHandler(self, event): MyForm = self.ParentForm @@ -426,7 +433,8 @@ class Element(): else: self.ParentForm.LastButtonClicked = '' self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() def __del__(self): try: @@ -883,7 +891,8 @@ class Spin(Element): else: self.ParentForm.LastButtonClicked = '' self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop def __del__(self): try: @@ -1225,7 +1234,8 @@ class Button(Element): self.ParentForm.LastButtonClicked = self.Key else: self.ParentForm.LastButtonClicked = self.ButtonText - self.ParentForm.TKroot.quit() # kick out of loop if read was called + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick out of loop if read was called # ------- Button Callback ------- # def ButtonCallBack(self): @@ -1300,7 +1310,8 @@ class Button(Element): self.ParentForm.LastButtonClicked = self.ButtonText self.ParentForm.FormRemainedOpen = False self.ParentForm._Close() - self.ParentForm.TKroot.quit() + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() if self.ParentForm.NonBlocking: self.ParentForm.TKroot.destroy() _my_windows.Decrement() @@ -1312,7 +1323,8 @@ class Button(Element): else: self.ParentForm.LastButtonClicked = self.ButtonText self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop + if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out + self.ParentForm.TKroot.quit() # kick the users out of the mainloop elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop self.ParentForm._Close() if self.ParentForm.NonBlocking: @@ -1329,7 +1341,8 @@ class Button(Element): if should_submit_window: self.ParentForm.LastButtonClicked = target_element.Key self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop return @@ -1984,7 +1997,8 @@ class Slider(Element): else: self.ParentForm.LastButtonClicked = '' self.ParentForm.FormRemainedOpen = True - self.ParentForm.TKroot.quit() # kick the users out of the mainloop + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop def __del__(self): super().__del__() @@ -2367,7 +2381,8 @@ class Menu(Element): # 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 + if self.ParentForm.CurrentlyRunningMainloop: + self.ParentForm.TKroot.quit() # kick the users out of the mainloop def __del__(self): super().__del__() @@ -2622,6 +2637,7 @@ class Window: self.NonBlocking = False self.TKroot = None self.TKrootDestroyed = False + self.CurrentlyRunningMainloop = False self.FormRemainedOpen = False self.TKAfterID = None self.ProgressBarColor = progress_bar_color @@ -2753,20 +2769,23 @@ class Window: def _TimeoutAlarmCallback(self): # first, get the results table built # modify the Results table in the parent FlexForm object + # print('TIMEOUT CALLBACK') if self.TimerCancelled: + # print('** timer was cancelled **') return self.LastButtonClicked = self.TimeoutKey self.FormRemainedOpen = True self.TKroot.quit() # kick the users out of the mainloop - def Read(self, timeout=None, timeout_key='_timeout_'): - if timeout == 0: + def Read(self, timeout=None, timeout_key=TIMEOUT_KEY): + if timeout == 0: # timeout of zero runs the old readnonblocking event, values = self.ReadNonBlocking() if event is None: event = timeout_key if values is None: event = None - return event, values + return event, values # make event None if values was None and return + # Read with a timeout self.Timeout = timeout self.TimeoutKey = timeout_key self.NonBlocking = False @@ -2775,27 +2794,48 @@ class Window: if not self.Shown: self.Show() else: + # if already have a button waiting, the return previously built results + if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime: + # print(f'*** Found previous clicked saved {self.LastButtonClicked}') + results = BuildResults(self, False, self) + self.LastButtonClicked = None + return results InitializeResults(self) # if the last button clicked was realtime, emulate a read non-blocking # the idea is to quickly return realtime buttons without any blocks until released if self.LastButtonClickedWasRealtime: + # print(f'RTime down {self.LastButtonClicked}' ) try: rc = self.TKroot.update() except: self.TKrootDestroyed = True _my_windows.Decrement() + print('ROOT Destroyed') results = BuildResults(self, False, self) if results[0] != None and results[0] != timeout_key: return results + else: + pass + + # else: + # print("** REALTIME PROBLEM FOUND **", results) + # normal read blocking code.... if timeout != None: self.TimerCancelled = False self.TKAfterID = self.TKroot.after(timeout, self._TimeoutAlarmCallback) + self.CurrentlyRunningMainloop = True + # print(f'In main {self.Title}') self.TKroot.mainloop() + # print('Out main') + self.CurrentlyRunningMainloop = False + # if self.LastButtonClicked != TIMEOUT_KEY: + # print(f'Window {self.Title} Last button clicked = {self.LastButtonClicked}') try: self.TKroot.after_cancel(self.TKAfterID) except: pass + # print('** tkafter cancel failed **') self.TimerCancelled = True if self.RootNeedsDestroying: self.TKroot.destroy() @@ -2803,8 +2843,12 @@ class Window: # 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() + # Determine return values if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: - return BuildResults(self, False, self) + results = BuildResults(self, False, self) + if not self.LastButtonClickedWasRealtime: + self.LastButtonClicked = None + return results else: return self.ReturnValues @@ -2820,6 +2864,7 @@ class Window: except: self.TKrootDestroyed = True _my_windows.Decrement() + # print("read failed") # return None, None return BuildResults(self, False, self) @@ -2928,7 +2973,8 @@ class Window: self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode) if not self.NonBlocking: BuildResults(self, False, self) - self.TKroot.quit() + if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.TKroot.quit() def _MouseWheelCallback(self, event): self.LastButtonClicked = None @@ -2936,7 +2982,8 @@ class Window: self.LastKeyboardEvent = 'MouseWheel:Down' if event.delta < 0 else 'MouseWheel:Up' if not self.NonBlocking: BuildResults(self, False, self) - self.TKroot.quit() + if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit! + self.TKroot.quit() def _Close(self): try: @@ -3004,6 +3051,12 @@ class Window: self._AlphaChannel = alpha self.TKroot.attributes('-alpha', alpha) + def BringToFront(self): + try: + self.TKroot.lift() + except: + pass + def __enter__(self): return self @@ -4531,7 +4584,9 @@ def StartupTK(my_flex_form): # my_flex_form.TKroot.protocol("WM_DELETE_WINDOW", my_flex_form.OnClosingCallback()) else: # it's a blocking form # print('..... CALLING MainLoop') + my_flex_form.CurrentlyRunningMainloop = True my_flex_form.TKroot.mainloop() + my_flex_form.CurrentlyRunningMainloop = False my_flex_form.TimerCancelled = True # print('..... BACK from MainLoop') if not my_flex_form.FormRemainedOpen: From 16240e1e3e5552bce12ab977208873a2dae8d823 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Thu, 25 Oct 2018 21:30:08 -0400 Subject: [PATCH 2/6] Demo showing suggested way to run multiple windows --- Demo_Multiple_Windows_Experimental.py | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Demo_Multiple_Windows_Experimental.py diff --git a/Demo_Multiple_Windows_Experimental.py b/Demo_Multiple_Windows_Experimental.py new file mode 100644 index 00000000..c4ed957f --- /dev/null +++ b/Demo_Multiple_Windows_Experimental.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +import sys +if sys.version_info[0] >= 3: + import PySimpleGUI as sg +else: + import PySimpleGUI27 as sg + +layout1 = [[ sg.Text('Window 1') ], + [sg.Input(do_not_clear=True)], + [ sg.RButton('Read')]] + +window1 = sg.Window('My new window', location=(800,800)).Layout(layout1) + + +layout2 = [[ sg.Text('Window 2') ], + [sg.Input(do_not_clear=False)], + [ sg.RButton('Read')]] + +window2 = sg.Window('My new window', location=(800, 925)).Layout(layout2) + + +layout3 = [[ sg.Text('Window 3') ], + [sg.Input(do_not_clear=False)], + [ sg.RButton('Read')]] + +window3 = sg.Window('My new window', location=(800,1050)).Layout(layout3) + + + +while True: # Event Loop + event, values = window1.Read(timeout=100) + if event is None: + break + elif event != '__timeout__': + print(event, values) + + event, values = window2.Read(timeout=0) + if event is None: + break + elif event != '__timeout__': + print(event, values) + + event, values = window3.Read(timeout=0) + if event is None: + break + elif event != '__timeout__': + print(event, values) From 4f61318a78129eddb4f92049ef7370ddff016586 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Fri, 26 Oct 2018 11:37:36 -0400 Subject: [PATCH 3/6] Tree Element - Now returns values - list of selected keys --- PySimpleGUI.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index b0308f53..bb65e95f 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -2503,11 +2503,18 @@ class Tree(Element): self.NumRows = num_rows self.Col0Width = col0_width self.TKTreeview = None + self.SelectedRows = [] super().__init__(ELEM_TYPE_TREE, text_color=text_color, background_color=background_color, font=font, pad=pad, key=key, tooltip=tooltip) return + + def treeview_selected(self, event): + selections = self.TKTreeview.selection() + self.SelectedRows = [x for x in selections] + + def __del__(self): super().__del__() @@ -3465,6 +3472,8 @@ def BuildResultsForSubform(form, initialize_only, top_level_form): value = None elif element.Type == ELEM_TYPE_TABLE: value = element.SelectedRows + elif element.Type == ELEM_TYPE_TREE: + value = element.SelectedRows else: value = None @@ -4466,6 +4475,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): ttk.Style().configure("Treeview", foreground=element.TextColor) element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') + treeview.bind("<>", element.treeview_selected) if element.Tooltip is not None: # tooltip element.TooltipObject = ToolTip(element.TKTreeview, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) From 6728b2cdc61e2e3f3ef3ec23c14261d55d9d1147 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Fri, 26 Oct 2018 11:44:22 -0400 Subject: [PATCH 4/6] Tree Element - new parameter show_expanded --- PySimpleGUI.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index bb65e95f..54d40d02 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -2467,7 +2467,7 @@ class Table(Element): # ---------------------------------------------------------------------- # class Tree(Element): def __init__(self, data=None, headings=None, visible_column_map=None, col_widths=None, col0_width=10, - def_col_width=10, auto_size_columns=True, max_col_width=20, select_mode=None, font=None, + def_col_width=10, auto_size_columns=True, max_col_width=20, select_mode=None, show_expanded=False, font=None, justification='right', text_color=None, background_color=None, num_rows=None, pad=None, key=None, tooltip=None): ''' @@ -2500,6 +2500,7 @@ class Tree(Element): self.Justification = justification self.InitialState = None self.SelectMode = select_mode + self.ShowExpanded = show_expanded self.NumRows = num_rows self.Col0Width = col0_width self.TKTreeview = None @@ -4461,7 +4462,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): def add_treeview_data(node): # print(f'Inserting {node.key} under parent {node.parent}') if node.key != '': - treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values) + treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=element.ShowExpanded) for node in node.children: add_treeview_data(node) @@ -4473,7 +4474,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): fieldbackground=element.BackgroundColor) if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: ttk.Style().configure("Treeview", foreground=element.TextColor) - element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') treeview.bind("<>", element.treeview_selected) if element.Tooltip is not None: # tooltip From 32a99344c9e2c9ad2388e274b0015afd1e97ee5c Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Fri, 26 Oct 2018 14:21:17 -0400 Subject: [PATCH 5/6] Test code for filetypes problem --- PySimpleGUI.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 54d40d02..834a3d47 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1269,7 +1269,7 @@ class Button(Element): should_submit_window = True except: pass - filetypes = [] if self.FileTypes is None else self.FileTypes + filetypes = (("ALL Files", "*.*"),) 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 != '': @@ -1279,6 +1279,7 @@ class Button(Element): except: pass elif self.BType == BUTTON_TYPE_BROWSE_FILE: + print('Browse button detected... filetypes is', filetypes) file_name = tk.filedialog.askopenfilename(filetypes=filetypes, initialdir=self.InitialFolder) # show the 'get file' dialog box if file_name != '': From e3a2c768b7830a22ea888cb159410c42ad61cc65 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Fri, 26 Oct 2018 14:47:54 -0400 Subject: [PATCH 6/6] Removed ability to set filetypes in Browse buttons for MACs. --- PySimpleGUI.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 834a3d47..a7184c23 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1279,9 +1279,10 @@ class Button(Element): except: pass elif self.BType == BUTTON_TYPE_BROWSE_FILE: - print('Browse button detected... filetypes is', filetypes) - file_name = tk.filedialog.askopenfilename(filetypes=filetypes, - initialdir=self.InitialFolder) # show the 'get file' dialog box + if sys.platform == 'darwin': + file_name = tk.filedialog.askopenfilename(initialdir=self.InitialFolder) # show the 'get file' dialog box + else: + 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) @@ -1291,13 +1292,19 @@ class Button(Element): 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 sys.platform == 'darwin': + file_name = tk.filedialog.askopenfilenames(initialdir=self.InitialFolder) + else: + 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, + if sys.platform == 'darwin': + file_name = tk.filedialog.asksaveasfilename(initialdir=self.InitialFolder) # show the 'get file' dialog box + else: + file_name = tk.filedialog.asksaveasfilename(filetypes=filetypes, initialdir=self.InitialFolder) # show the 'get file' dialog box if file_name != '': strvar.set(file_name)