diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 99d2bc32..13662eda 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -17,6 +17,7 @@ else: import types import datetime +import time import textwrap import pickle import calendar @@ -26,7 +27,6 @@ g_time_start = 0 g_time_end = 0 g_time_delta = 0 -import time def TimerStart(): @@ -3317,7 +3317,7 @@ class TreeData(object): def __init__(self): self.tree_dict = {} - self.root_node = self.Node("", "", 'root', []) + self.root_node = self.Node("", "", 'root', [], None) self.tree_dict[""] = self.root_node def _AddNode(self, key, node): @@ -3757,7 +3757,7 @@ class Window: FillFormWithValues(self, values_dict) return self - def FindElement(self, key, silent_on_error=None): + def FindElement(self, key, silent_on_error=False): element = _FindElementFromKeyInSubForm(self, key) if element is None: if not silent_on_error: @@ -5047,8 +5047,9 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form:Window): # print(style_name) combostyle = ttk.Style() + unique_field = str(time.time()).replace('.','') + '.TCombobox.field' # Creates a unique name for each field element(Sure there is a better way to do this) - unique_field = str(datetime.datetime.today().timestamp()).replace('.','') + '.TCombobox.field' + # unique_field = str(datetime.datetime.today().timestamp()).replace('.','') + '.TCombobox.field' # unique_field = str(randint(1,50000000)) + '.TCombobox.field' # print(unique_field) @@ -5726,7 +5727,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form:Window): except: width = element.DefaultColumnWidth treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - def add_treeview_data(node): # print(f'Inserting {node.key} under parent {node.parent}') if node.key != '': @@ -5736,8 +5736,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form:Window): else: photo = tk.PhotoImage(file=node.icon) node.photo = photo - # except: - # self.photo = None treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=element.ShowExpanded, image=node.photo) else: treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=element.ShowExpanded) diff --git a/PySimpleGUI27.py b/PySimpleGUI27.py index 28d720df..69a6b723 100644 --- a/PySimpleGUI27.py +++ b/PySimpleGUI27.py @@ -29,15 +29,16 @@ else: import types import datetime +import time import textwrap import pickle import calendar +from random import randint g_time_start = 0 g_time_end = 0 g_time_delta = 0 -import time def TimerStart(): @@ -73,8 +74,9 @@ def TimerStop(): # ----====----====----==== Constants the user CAN safely change ====----====----====----# +# Base64 encoded GIF file +DEFAULT_BASE64_ICON = b'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' -DEFAULT_BASE64_ICON = b'iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAMAAACrZuH4AAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAMGmYMGqZMWqaMmubMmycM22dNGuZNm2bNm6bNG2dN26cNG6dNG6eNW+fN3CfOHCeOXGfNXCgNnGhN3KiOHOjOXSjOHSkOnWmOnamOnanPHSiPXakPnalO3eoPnimO3ioPHioPHmpPHmqPXqqPnurPnusPnytP3yuQHimQnurQn2sQH2uQX6uQH6vR32qRn+sSXujSHynTH2mTn+nSX6pQH6wTIGsTYKuTYSvQoCxQoCyRIK0R4S1RYS2Roa4SIe4SIe6SIi7Soq7SYm8SYq8Sou+TY2/UYStUYWvVIWtUYeyVoewUIi0VIizUI6+Vo+8WImxXJG5YI2xZI+xZ5CzZJC0ZpG1b5a3apW4aZm/cZi4dJ2/eJ69fJ+9XZfEZZnCZJzHaZ/Jdp/AeKTI/tM8/9Q7/9Q8/9Q9/9Q+/tQ//9VA/9ZA/9ZB/9ZC/9dD/9ZE/tdJ/9dK/9hF/9hG/9hH/9hI/9hJ/9hK/9lL/9pK/9pL/thO/9pM/9pN/9tO/9tP/9xP/tpR/9xQ/9xR/9xS/9xT/91U/91V/t1W/95W/95X/95Y/95Z/99a/99b/txf/txh/txk/t5l/t1q/t5v/+Bb/+Bc/+Bd/+Be/+Bf/+Bg/+Fh/+Fi/+Jh/+Ji/uJk/uJl/+Jm/+Rm/uJo/+Ro/+Rr/+Zr/+Vs/+Vu/+Zs/+Zu/uF0/uVw/+dw/+dz/+d2/uB5/uB6/uJ9/uR7/uR+/uV//+hx/+hy/+h0/+h2/+l4/+l7/+h8gKXDg6vLgazOhKzMiqrEj6/KhK/Qka/Hk7HJlLHJlLPMmLTLmbbOkLXSmLvXn77XoLrPpr/Tn8DaocLdpcHYrcjdssfZus/g/uOC/uOH/uaB/uWE/uaF/uWK/+qA/uqH/uqI/uuN/uyM/ueS/ueW/ueY/umQ/uqQ/uuS/uuW/uyU/uyX/uqa/uue/uye/uyf/u6f/uyq/u+r/u+t/vCm/vCp/vCu/vCy/vC2/vK2/vO8/vO/wtTjwtXlzdrl/vTA/vPQAAAAiNpY5gAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAAFw8AABcPASe7rwsAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAKUSURBVDhPhdB3WE1xHMdxt5JV0dANoUiyd8kqkey996xclUuTlEKidO3qVnTbhIyMW/bee5NskjJLmR/f3++cK/94vP76Ps/n/Zx7z6mE/6koJowcK154vvHOL/GsKCZXkUgkWlf4vWGWq5tsDz+JWIzSokAiqXGe7nWu3HxhEYof7fhOqp1GtptQuMruVhQdxZ05U5G47tYUHbQ4oah6Fg9Z4ubm7i57JhQjdHS0RSzUPoG17u6zZTKZh8c8XlytqW9YWUOH1LqFOZ6enl5ec+XybFb0rweM1tPTM6yuq6vLs0lYJJfLvb19fHwDWGF0jh5lYNAe4/QFemOwxtfXz8/fPyBgwVMqzAcCF4ybAZ2MRCexJGBhYGBQUHDw4u1UHDG1G2ZqB/Q1MTHmzAE+kpCwL1RghlTaBt/6SaXS2kx9YH1IaOjSZST8vfA9JtoDnSngGgL7wkg4WVkofA9mcF1Sx8zMzBK4v3wFiYiMVLxlEy9u21syFhYNmgN7IyJXEYViNZvEYoCVVWOmUVvgQVSUQqGIjolRFvOAFd8HWVs34VoA+6OjY2JjY5Vxm4BC1UuhGG5jY9OUaQXci1MqlfHx8YmqjyhOViW9ZsUN29akJRmPFwkJCZsTSXIpilJffXiTzorLXYgtcxRJKpUqKTklJQ0oSt9FP/EonxVdNY4jla1kK4q2ZB6mIr+AipvduzFUzMSOtLT09IyMzMxtJKug/F0u/6dTexAWDcXXLGEjapKjfsILOLKEuYiSnTQeYCt3UHhbwEHjGMrETfBJU5zq5dSTcXC8hLJccSWP2cgLXHPu7cQNAcpyxF1dyjehAKb0cSYUAOXCUw6V8OFPgevTXFymC+fPPLU677Nw/1X8A/AbfAKGulaqFlIAAAAASUVORK5CYII=' if sys.version_info[0] >= 3: DEFAULT_WINDOW_ICON = DEFAULT_BASE64_ICON else: @@ -369,7 +371,7 @@ class Element(object): self.TKEntry = None self.TKImage = None - self.ParentForm = None + self.ParentForm = None # type: Window self.ParentContainer = None # will be a Form, Column, or Frame element self.TextInputDefault = None self.Position = (0, 0) # Default position Row 0, Col 0 @@ -379,7 +381,7 @@ class Element(object): self.Tooltip = tooltip self.TooltipObject = None self.Visible = visible - + self.TKRightClickMenu = None def RightClickMenuCallback(self, event): self.TKRightClickMenu.tk_popup(event.x_root, event.y_root, 0) @@ -1414,9 +1416,9 @@ class Output(Element): self._TKOut.output.delete('1.0', tk.END) self._TKOut.output.insert(tk.END, value) if visible is False: - self._TKOut.pack_forget() + self._TKOut.frame.pack_forget() elif visible is True: - self._TKOut.pack() + self._TKOut.frame.pack() def __del__(self): try: @@ -1649,10 +1651,15 @@ class Button(Element): self.TKButton.image = image if image_filename is not None: self.TKButton.config(highlightthickness=0) - photo = tk.PhotoImage(file=image_filename) - width, height = photo.width(), photo.height() - self.TKButton.config(image=photo, width=width, height=height) - self.TKButton.image = photo + image = tk.PhotoImage(file=image_filename) + if image_size is not None: + width, height = image_size + else: + width, height = image.width(), image.height() + if image_subsample: + image = image.subsample(image_subsample) + self.TKButton.config(image=image, width=width, height=height) + self.TKButton.image = image if visible is False: self.TKButton.pack_forget() elif visible is True: @@ -1721,6 +1728,7 @@ class ButtonMenu(Element): self.MenuItemChosen = None self.Tearoff = tearoff self.TKButtonMenu = None + self.TKMenu = None # self.temp_size = size if size != (NONE, NONE) else super().__init__(ELEM_TYPE_BUTTONMENU, size=size, font=font, pad=pad, key=key, tooltip=tooltip, text_color=self.TextColor, background_color=self.BackgroundColor, visible=visible) @@ -1741,32 +1749,11 @@ class ButtonMenu(Element): if menu_definition is not None: self.TKMenu = tk.Menu(self.TKButtonMenu, tearoff=self.Tearoff) # create the menubar AddMenuItem(self.TKMenu, menu_definition[1], self) - # for menu_entry in menu_definition: - # # print(f'Adding a Menubar ENTRY {menu_entry}') - # baritem = tk.Menu(menubar, tearoff=self.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:] - # if menu_entry[0][0] == MENU_DISABLED_CHARACTER: - # menubar.add_cascade(label=menu_entry[0][len(MENU_DISABLED_CHARACTER):], menu=baritem, underline=pos) - # menubar.entryconfig(menu_entry[0][len(MENU_DISABLED_CHARACTER):], state='disabled') - # else: - # menubar.add_cascade(label=menu_entry[0], menu=baritem, underline=pos) - # - # if len(menu_entry) > 1: - # AddMenuItem(baritem, menu_entry[1], self) self.TKButtonMenu.configure(menu=self.TKMenu) - - def UpdateQt(self, menu_definition=None, text=None, button_color=(None, None), font=None, visible=None): - if menu_definition is not None: - menu_def = menu_definition - qmenu = QMenu(self.QT_QPushButton) - qmenu.setTitle(menu_def[0]) - AddMenuItem(qmenu, menu_def[1], self) - self.QT_QPushButton.setMenu(qmenu) - super().Update(self.QT_QPushButton, background_color=button_color[1], text_color=button_color[0], font=font, visible=visible) + if visible is False: + self.TKButtonMenu.pack_forget() + elif visible is True: + self.TKButtonMenu.pack() @@ -2548,6 +2535,30 @@ class Slider(Element): super().__del__() +# ---------------------------------------------------------------------- # +# TkScrollableFrame (Used by Column) # +# ---------------------------------------------------------------------- # +class TkFixedFrame(tk.Frame): + def __init__(self, master, **kwargs): + tk.Frame.__init__(self, master, **kwargs) + + self.canvas = tk.Canvas(self) + + self.canvas.pack(side="left", fill="both", expand=True) + + # 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) + + + # ---------------------------------------------------------------------- # # TkScrollableFrame (Used by Column) # # ---------------------------------------------------------------------- # @@ -3048,7 +3059,7 @@ MenuBar = Menu # another name for Menu to make it clear it's the Menu B # ---------------------------------------------------------------------- # 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, num_rows=None, row_height=None, font=None, justification='right', text_color=None, background_color=None, alternating_row_color=None, + auto_size_columns=True, max_col_width=20, select_mode=None, display_row_numbers=False, num_rows=None, row_height=None, font=None, justification='right', text_color=None, background_color=None, alternating_row_color=None, row_colors=None, vertical_scroll_only=True, size=(None, None), change_submits=False, enable_events=False, bind_return_key=False, pad=None, key=None, tooltip=None, right_click_menu=None, visible=True): ''' Table @@ -3095,12 +3106,14 @@ class Table(Element): self.RowHeight = row_height self.TKTreeview = None self.AlternatingRowColor = alternating_row_color + self.VerticalScrollOnly = vertical_scroll_only self.SelectedRows = [] self.ChangeSubmits = change_submits or enable_events self.BindReturnKey = bind_return_key self.StartingRowNumber = 0 # When displaying row numbers, where to start self.RowHeaderText = 'Row' self.RightClickMenu = right_click_menu + self.RowColors = row_colors super().__init__(ELEM_TYPE_TABLE, text_color=text_color, background_color=background_color, font=font, size=size, pad=pad, key=key, tooltip=tooltip, visible=visible) @@ -3167,7 +3180,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, show_expanded=False, change_submits=False, enable_events=False, font=None, justification='right', text_color=None, background_color=None, num_rows=None, pad=None, key=None, tooltip=None,right_click_menu=None, visible=True): + def_col_width=10, auto_size_columns=True, max_col_width=20, select_mode=None, show_expanded=False, change_submits=False, enable_events=False, font=None, justification='right', text_color=None, background_color=None, num_rows=None, row_height=None, pad=None, key=None, tooltip=None,right_click_menu=None, visible=True): ''' Tree :param data: @@ -3212,6 +3225,9 @@ class Tree(Element): self.SelectedRows = [] self.ChangeSubmits = change_submits or enable_events self.RightClickMenu = right_click_menu + self.RowHeight = row_height + self.IconList = {} + super().__init__(ELEM_TYPE_TREE, text_color=text_color, background_color=background_color, font=font, pad=pad, key=key, tooltip=tooltip, visible=visible) @@ -3234,13 +3250,26 @@ class Tree(Element): def add_treeview_data(self, node): # print(f'Inserting {node.key} under parent {node.parent}') if node.key != '': - self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, - open=self.ShowExpanded) + if node.icon: + try: + if type(node.icon) is bytes: + photo = tk.PhotoImage(data=node.icon) + else: + photo = tk.PhotoImage(file=node.icon) + node.photo = photo + self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, + open=self.ShowExpanded, image=node.photo) + except: + self.photo = None + else: + self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, + open=self.ShowExpanded) + for node in node.children: self.add_treeview_data(node) - def Update(self, values=None, key=None, value=None, text=None, visible=None): + def Update(self, values=None, key=None, value=None, text=None, icon=None, visible=None): if values is not None: children = self.TKTreeview.get_children() for i in children: @@ -3256,6 +3285,16 @@ class Tree(Element): self.TKTreeview.item(key, values=value) if text is not None: self.TKTreeview.item(key, text=text) + if icon is not None: + try: + if type(icon) is bytes: + photo = tk.PhotoImage(data=icon) + else: + photo = tk.PhotoImage(file=icon) + self.TKTreeview.item(key, image=photo) + self.IconList[key] = photo # save so that it's not deleted (save reference) + except: + pass item = self.TKTreeview.item(key) if visible is False: self.TKTreeview.pack_forget() @@ -3269,26 +3308,27 @@ class Tree(Element): class TreeData(object): class Node(object): - def __init__(self, parent, key, text, values): + def __init__(self, parent, key, text, values, icon=None): self.parent = parent self.children = [] self.key = key self.text = text self.values = values + self.icon = icon def _Add(self, node): self.children.append(node) def __init__(self): self.tree_dict = {} - self.root_node = self.Node("", "", 'root', []) + self.root_node = self.Node("", "", 'root', [], None) self.tree_dict[""] = self.root_node def _AddNode(self, key, node): self.tree_dict[key] = node - def Insert(self, parent, key, text, values): - node = self.Node(parent, key, text, values) + def Insert(self, parent, key, text, values, icon=None): + node = self.Node(parent, key, text, values, icon) self.tree_dict[key] = node parent_node = self.tree_dict[parent] parent_node._Add(node) @@ -3396,7 +3436,7 @@ class Window(object): self.Font = font if font else DEFAULT_FONT self.RadioDict = {} self.BorderDepth = border_depth - self.WindowIcon = icon if icon is not None else Window.user_defined_icon + self.WindowIcon = Window.user_defined_icon if Window.user_defined_icon is not None else icon if icon is not None else DEFAULT_WINDOW_ICON self.AutoClose = auto_close self.NonBlocking = False self.TKroot = None @@ -3594,6 +3634,8 @@ class Window(object): # 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: + self.LastButtonClickedWasRealtime = False # stops from generating events until something changes + # print(f'RTime down {self.LastButtonClicked}' ) try: rc = self.TKroot.update() @@ -3719,15 +3761,18 @@ class Window(object): FillFormWithValues(self, values_dict) return self - def FindElement(self, key): + def FindElement(self, key, silent_on_error=False): element = _FindElementFromKeyInSubForm(self, key) 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) + if not silent_on_error: + 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) + else: + return False return element Element = FindElement # Shortcut function @@ -4340,7 +4385,7 @@ def BuildResultsForSubform(form, initialize_only, top_level_form): value = 0 elif element.Type == ELEM_TYPE_INPUT_SLIDER: try: - value = element.TKIntVar.get() + value = float(element.TKScale.get()) except: value = 0 elif element.Type == ELEM_TYPE_INPUT_MULTILINE: @@ -4687,10 +4732,18 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): highlightthickness=0) element.TKColFrame.config(background=element.BackgroundColor, borderwidth=0, highlightthickness=0) else: - element.TKColFrame = tk.Frame(tk_row_frame) if element.Size != (None, None): - element.TKColFrame.configure(width=element.Size[0], height=element.Size[1]) - PackFormIntoFrame(element, element.TKColFrame, toplevel_form) + element.TKColFrame = TkFixedFrame(tk_row_frame) + PackFormIntoFrame(element, element.TKColFrame.TKFrame, toplevel_form) + element.TKColFrame.TKFrame.update() + if element.Size[1] is not None: + element.TKColFrame.canvas.config(height=element.Size[1]) + elif element.Size[0] is not None: + element.TKColFrame.canvas.config(width=element.Size[0]) + else: + element.TKColFrame = tk.Frame(tk_row_frame) + PackFormIntoFrame(element, element.TKColFrame, toplevel_form) + element.TKColFrame.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1], expand=True, fill='both') if element.Visible is False: element.TKColFrame.pack_forget() @@ -4946,7 +4999,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT (Single Line) element ------------------------- # + # ------------------------- INPUT element ------------------------- # elif element_type == ELEM_TYPE_INPUT_TEXT: default_text = element.DefaultText element.TKStringVar = tk.StringVar() @@ -4983,7 +5036,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu element.TKEntry.bind('', element.RightClickMenuCallback) - # ------------------------- COMBO BOX (Drop Down) element ------------------------- # + # ------------------------- COMBOBOX 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: @@ -4993,66 +5046,49 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKStringVar = tk.StringVar() style_name = 'TCombobox' if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: - # print('Combo special style', element.TextColor, element.BackgroundColor) - style_name = 'PSG.TCombobox' + # Creates 1 style per Text Color/ Background Color combination + style_name = element.TextColor + element.BackgroundColor + '.TCombobox' + # print(style_name) combostyle = tkinter.ttk.Style() - # - # combostyle.map("C.TButton", - # foreground=[('pressed', 'red'), ('active', 'blue')], - # background=[('pressed', '!disabled', 'black'), ('active', 'white')] - # ) - # combostyle.map('PSG.TCombobox', background=[('selected', 'green')]) - # combostyle.configure('PSG.TCombobox.Listbox',foreground='green') - # combostyle.map('PSG.TCombobox', foreground=[('active','purple')]) - # combostyle.map('PSG.TCombobox.textarea', foreground=[('active','purple')]) - # combostyle.map('PSG.TCombobox.rightdownarrow', arrowcolor=[('active','purple')]) - # combostyle.configure('PSG.TCombobox.TEntry', background='red') - # combostyle.configure('PSG.TCombobox', background=element.BackgroundColor) - combostyle.configure('PSG.TCombobox', foreground=element.TextColor) # WORKS - combostyle.configure('PSG.TCombobox', selectbackground='gray70') # WORKS - combostyle.configure('PSG.TCombobox', selectforeground=element.TextColor) # WORKS - # combostyle.configure('PSG.TCombobox.Listbox', background='purple') - # toplevel_form.TKroot.option_add("*TCombobox*Background", element.BackgroundColor) # WORK for drop-down list (Changes all) - # combostyle.map('PSG.TCombobox', background=[('active', 'purple'), ('disabled', 'purple')]) - # combostyle.configure('PSG.TCombobox.PopdownFrame', background=element.BackgroundColor) - # combostyle.configure('PSG.TCombobox.field', fieldbackground=element.BackgroundColor) - # combostyle.configure('PSG.TCombobox.Listbox', background=element.BackgroundColor) - # print(combostyle.element_names()) - # print(combostyle.element_options('PSG.TCombobox')) - # try: - # combostyle.theme_create('combostyle', - # settings={'TCombobox': - # {'configure': - # {'selectbackground': 'gray50', - # 'fieldbackground': element.BackgroundColor, - # 'foreground': text_color, - # 'background': element.BackgroundColor} - # }}) - # except: - # try: - # combostyle.theme_settings('combostyle', - # settings={'TCombobox': - # {'configure': - # {'selectbackground': 'gray50', - # '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') + + + # Creates a unique name for each field element(Sure there is a better way to do this) + unique_field = str(time.time()).replace('.','') + '.TCombobox.field' + # unique_field = str(randint(1,50000000)) + '.TCombobox.field' + + # print(unique_field) + # Clones over the TCombobox.field element from the "alt" theme. + # This is what will allow us to change the background color without altering the whole programs theme + combostyle.element_create(unique_field, "from", "alt") + + # Create widget layout using cloned "alt" field + combostyle.layout(style_name, [ + (unique_field, {'children': [('Combobox.downarrow', {'side': 'right', 'sticky': 'ns'}), + ('Combobox.padding', + {'children': [('Combobox.focus', + {'children': [('Combobox.textarea', + {'sticky': 'nswe'})], + 'expand': '1', + 'sticky': 'nswe'})], + 'expand': '1', + 'sticky': 'nswe'})], + 'sticky': 'nswe'})]) + + # Copy default TCombobox settings + # Getting an error on this line of code + # combostyle.configure(style_name, *combostyle.configure("TCombobox")) + + # Set individual widget options + combostyle.configure(style_name, foreground=element.TextColor) + combostyle.configure(style_name, selectbackground='gray70') + combostyle.configure(style_name, fieldbackground=element.BackgroundColor) + combostyle.configure(style_name, selectforeground=element.TextColor) element.TKCombo = tkinter.ttk.Combobox(tk_row_frame, width=width, textvariable=element.TKStringVar, font=font, style=style_name) 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=elementpad[0], pady=elementpad[1]) if element.Visible is False: element.TKCombo.pack_forget() @@ -5071,7 +5107,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKCombo['state'] = 'disabled' 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 ------------------------- # + # ------------------------- OPTION MENU Element (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: @@ -5170,7 +5206,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu element.TKText.bind('', element.RightClickMenuCallback) - # ------------------------- INPUT CHECKBOX element ------------------------- # + # ------------------------- CHECKBOX element ------------------------- # elif element_type == ELEM_TYPE_INPUT_CHECKBOX: width = 0 if auto_size_text else element_size[0] default_value = element.InitialState @@ -5217,7 +5253,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKProgressBar.TKProgressBarForReal.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1]) if element.Visible is False: element.TKProgressBar.TKProgressBarForReal.pack_forget() - # ------------------------- INPUT RADIO BUTTON element ------------------------- # + # ------------------------- RADIO BUTTON element ------------------------- # elif element_type == ELEM_TYPE_INPUT_RADIO: width = 0 if auto_size_text else element_size[0] default_value = element.InitialState @@ -5251,7 +5287,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKRadio.pack_forget() if element.Tooltip is not None: element.TooltipObject = ToolTip(element.TKRadio, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) - # ------------------------- INPUT SPIN Box element ------------------------- # + # ------------------------- SPIN element ------------------------- # elif element_type == ELEM_TYPE_INPUT_SPIN: width, height = element_size width = 0 if auto_size_text else element_size[0] @@ -5282,7 +5318,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): pad=elementpad) element._TKOut.pack(side=tk.LEFT, expand=True, fill='both') if element.Visible is False: - element._TKOut.pack_forget() + element._TKOut.frame.pack_forget() if element.Tooltip is not None: element.TooltipObject = ToolTip(element._TKOut, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) if element.RightClickMenu or toplevel_form.RightClickMenu: @@ -5313,7 +5349,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): else: element.tktext_label = tk.Label(tk_row_frame, width=width, height=height, bd=border_depth) if element.BackgroundColor is not None: - element.tktext_label.config(background=element.BackgroundColor); + element.tktext_label.config(background=element.BackgroundColor) element.tktext_label.image = photo # tktext_label.configure(anchor=tk.NW, image=photo) @@ -5556,6 +5592,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TooltipObject = ToolTip(element.TKScale, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) # ------------------------- TABLE element ------------------------- # elif element_type == ELEM_TYPE_TABLE: + element = element # type: Table frame = tk.Frame(tk_row_frame) height = element.NumRows @@ -5575,7 +5612,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): except: column_widths[i] = col_width if element.ColumnsToDisplay is None: - displaycolumns = element.ColumnHeadings + displaycolumns = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0] else: displaycolumns = [] for i, should_display in enumerate(element.ColumnsToDisplay): @@ -5587,13 +5624,15 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): column_headings = [element.RowHeaderText, ] + element.ColumnHeadings element.TKTreeview = tkinter.ttk.Treeview(frame, columns=column_headings, displaycolumns=displaycolumns, show='headings', height=height, - selectmode=element.SelectMode) + selectmode=element.SelectMode,) treeview = element.TKTreeview if element.DisplayRowNumbers: treeview.heading(element.RowHeaderText, text=element.RowHeaderText) # make a dummy heading treeview.column(element.RowHeaderText, width=50, anchor=anchor) - for i, heading in enumerate(element.ColumnHeadings): - treeview.heading(heading, text=heading) + + headings = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0] + for i, heading in enumerate(headings): + # treeview.heading(heading, text=heading) if element.AutoSizeColumns: width = max(column_widths[i], len(heading)) else: @@ -5601,15 +5640,23 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): width = element.ColumnWidths[i] except: width = element.DefaultColumnWidth - treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) + # Insert values into the tree for i, value in enumerate(element.Values): if element.DisplayRowNumbers: value = [i+element.StartingRowNumber] + value - id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i % 2) - if element.AlternatingRowColor is not None: - treeview.tag_configure(1, background=element.AlternatingRowColor) + id = treeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i) + if element.AlternatingRowColor is not None: # alternating colors + for row in range(0, len(element.Values), 2): + treeview.tag_configure(row, background=element.AlternatingRowColor) + if element.RowColors is not None: # individual row colors + for row_def in element.RowColors: + if len(row_def) == 2: # only background is specified + treeview.tag_configure(row_def[0], background=row_def[1]) + else: + treeview.tag_configure(row_def[0], background=row_def[2], foreground=row_def[1]) + if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: tkinter.ttk.Style().configure("Treeview", background=element.BackgroundColor, fieldbackground=element.BackgroundColor) @@ -5622,9 +5669,17 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.BindReturnKey: treeview.bind('', element.treeview_double_click) treeview.bind('', element.treeview_double_click) + scrollbar = tk.Scrollbar(frame) scrollbar.pack(side=tk.RIGHT, fill='y') scrollbar.config(command=treeview.yview) + + if not element.VerticalScrollOnly: + hscrollbar = tk.Scrollbar(frame, orient=tk.HORIZONTAL) + hscrollbar.pack(side=tk.BOTTOM, fill='x') + hscrollbar.config(command=treeview.xview) + treeview.configure(xscrollcommand=hscrollbar.set) + treeview.configure(yscrollcommand=scrollbar.set) element.TKTreeview.pack(side=tk.LEFT, expand=True, padx=0, pady=0, fill='both') @@ -5642,6 +5697,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKTreeview.bind('', element.RightClickMenuCallback) # ------------------------- Tree element ------------------------- # elif element_type == ELEM_TYPE_TREE: + element = element #type: Tree frame = tk.Frame(tk_row_frame) height = element.NumRows @@ -5663,7 +5719,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): # ------------- GET THE TREEVIEW WIDGET ------------- element.TKTreeview = tkinter.ttk.Treeview(frame, columns=column_headings, displaycolumns=displaycolumns, show='tree headings', height=height, - selectmode=element.SelectMode, ) + selectmode=element.SelectMode) treeview = element.TKTreeview for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings treeview.heading(heading, text=heading) @@ -5675,11 +5731,19 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): except: width = element.DefaultColumnWidth treeview.column(heading, width=width * CharWidthInPixels(), anchor=anchor) - 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, open=element.ShowExpanded) + if node.icon: + if type(node.icon) is bytes: + photo = tk.PhotoImage(data=node.icon) + else: + photo = tk.PhotoImage(file=node.icon) + node.photo = photo + treeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values, open=element.ShowExpanded, image=node.photo) + else: + 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) @@ -5692,6 +5756,9 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT: tkinter.ttk.Style().configure("Treeview", foreground=element.TextColor) + tkinter.ttk.Style().configure("Treeview", font=font) + if element.RowHeight: + tkinter.ttk.Style().configure("Treeview", rowheight=element.RowHeight) scrollbar = tk.Scrollbar(frame) scrollbar.pack(side=tk.RIGHT, fill='y') scrollbar.config(command=treeview.yview) @@ -5712,6 +5779,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKTreeview.bind('', element.RightClickMenuCallback) # ------------------------- Separator element ------------------------- # elif element_type == ELEM_TYPE_SEPARATOR: + element = element # type: VerticalSeparator separator = tkinter.ttk.Separator(tk_row_frame, orient=element.Orientation, ) separator.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1], fill='both', expand=True) # ------------------------- StatusBar element ------------------------- # @@ -5819,7 +5887,6 @@ def ConvertFlexToTK(MyFlexForm): # ----====----====----====----====----==== STARTUP TK ====----====----====----====----====----# def StartupTK(my_flex_form): # global _my_windows - # ow = _my_windows.NumOpenWindows ow = Window.NumOpenWindows # print('Starting TK open Windows = {}'.format(ow)) @@ -7734,7 +7801,7 @@ def main(): frame2 = [ [Listbox(['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(20, 5))], - [Combo(['Combo item 1', ], size=(20, 3))], + [Combo(['Combo item 1', ], size=(20, 3), text_color='red', background_color='red')], [Spin([1, 2, 3], size=(4, 3))], ] diff --git a/readme.md b/readme.md index 803f6913..8846c8c1 100644 --- a/readme.md +++ b/readme.md @@ -31,9 +31,9 @@ -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.21.0-red.svg?longCache=true&style=for-the-badge) +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.22.0-red.svg?longCache=true&style=for-the-badge) -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.21.0-blue.svg?longCache=true&style=for-the-badge) +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.22.0-blue.svg?longCache=true&style=for-the-badge) ![Python Version](https://img.shields.io/badge/PySimpleGUIQt_For_Python_3.x_Version-0.22.0-orange.svg?longCache=true&style=for-the-badge) @@ -1507,7 +1507,7 @@ There are a few methods (functions) that you will see in this document that act window.Close() - To close your window, if a button hasn't already closed it window.Disable() - Use to disable the window inputwhen 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.FindElement(key, silent_on_error=False) - Returns the element that has a matching key value window.Move(x,y) - Moves window to location x,y on screen' window.SetAlpha(alpha) - Changes window transparency window.BringToFront() - Brings the window to the top of other windows on the screen @@ -1532,6 +1532,9 @@ window = sg.Window('My window title').Layout(layout) #### Finalize() Call to force a window to go through the final stages of initialization. This will cause the tkinter resources to be allocated so that they can then be modified. This also causes your window to appear. If you do not want your window to appear when Finalize is called, then set the Alpha to 0 in your window's creation parameters. + +If you want to call an element's Update method or call a Graph element's drawing primitives, you ***must*** either call Read or Finalize prior to making those calls. + #### Read(timeout=None, timeout_key='__TIMEOUT_ _ ') @@ -3091,6 +3094,8 @@ Table( values, text_color=None, background_color=None, alternating_row_color=None, + row_colors=None, + vertical_scroll_only=True, size=(None,None), change_submits=False, enable_events=False, @@ -3098,6 +3103,7 @@ Table( values, pad=None, key=None, tooltip=None, + right_click_menu=None, visible=True): ``` @@ -3117,6 +3123,8 @@ font - font for table entries justification - left, right, center text_color - color of text alternating row color - if set will change background color for alternating rows +row_colors - list of tuples representing (row_number, color) e.g. row_colors = ((5, 'white', 'blue'), (0,'red'), (15,'yellow')) +vertical_scroll_only - if True will not show a horizontal scrollbar. NOTE - will have to disable to get horizontal scrollbars background_color - cell background color size - (None, number of rows) - don't use, use num_rows instead enable_events - will return a 'row selected' event when row is selected @@ -3147,7 +3155,8 @@ The Tree Element and Table Element are close cousins. Many of the parameters f Tree( data=None, headings=None, visible_column_map=None, - col_widths=None, col0_width=10, + col_widths=None, + col0_width=10, def_col_width=10, auto_size_columns=True, max_col_width=20, @@ -3160,6 +3169,7 @@ Tree( data=None, text_color=None, background_color=None, num_rows=None, + row_height=None, pad=None, key=None, tooltip=None, @@ -3181,7 +3191,8 @@ class Tree(data=None - data in TreeData format 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 + num_rows=None - number of rows to display + row_height=None - height of rows in pixels pad=None - element padding key=None - key for element tooltip=None - tooltip @@ -3191,7 +3202,13 @@ Unlike Tables there is no standard format for trees. Thus the data structure pa * Get a TreeData Object * "Insert" data into the tree * Pass the filled in TreeData object to Tree Element - + +#### TreeData format +```python +def TreeData() +def Insert(self, parent, key, text, values, icon=None) +``` + To "insert" data into the tree the TreeData method Insert is called. `Insert(parent_key, key, display_text, values)` @@ -3211,6 +3228,14 @@ Note that you ***can*** use the same values for display_text and keys. The only When Reading a window the Table Element will return a list of rows that are selected by the user. The list will be empty is no rows are selected. +#### Icons on Tree Entries + +If you wish to show an icon next to a tree item, then you specify the icon in the call to `Insert`. You pass in a filename or a Base64 bytes string using the optional `icon` parameter. + +Here is the result of showing an icon with a tree entry. + +![image](https://user-images.githubusercontent.com/13696193/51087270-2b561e80-171f-11e9-8260-6142ea9b1137.png) + ## Tab and Tab Group Elements @@ -4423,7 +4448,7 @@ Listboxes are still without scrollwheels. The mouse can drag to see more items. 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. +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 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. @@ -4773,7 +4798,7 @@ Emergency patch release... going out same day as previous release ## 3.20.0 & 1.20.0 18-Dec-2018 * New Pane Element -* Graphh.DeleteFigure method +* Graph.DeleteFigure method * disable_minimize - New parameter for Window * Fix for 2.7 menus * Debug Window no longer re-routes stdout by default @@ -4806,6 +4831,24 @@ Emergency patch release... going out same day as previous release * Attempted to use Styles better with Combobox * Fixed bug blocking setting bar colors in OneLineProgressMeter +# 3.22.0 PySimpleGUI / 1.22.0 PySimpleGUI27 + +* Added type hints to some portions of the code +* Output element can be made invisible +* Image sizing and subsample for Button images +* Invisibility for ButtonMenus +* Attempt at specifying size of Column elements (limited success) +* Table Element + * New row_colors parameter + * New vertical_scroll_only parameter - NOTE - will have to disable to get horizontal scrollbars +* Tree Element + * New row_height parameter + * New feature - Icons for tree entries using filename or Base64 images +* Fix for bug sending back continuous mouse events +* New parameter silence_on_error for FindElement / Element calls +* Slider returns float now +* Fix for Menus when using Python 2.7 +* Combobox Styling (again) ### Upcoming @@ -4913,4 +4956,7 @@ For Python questions, I simply start my query with 'Python'. Let's say you forg 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 +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