Release 3.17.0

This commit is contained in:
MikeTheWatchGuy 2018-12-02 14:28:52 -05:00
parent a59dca250d
commit c371aa68eb
4 changed files with 511 additions and 163 deletions

View File

@ -86,6 +86,7 @@ DEFAULT_DEBUG_WINDOW_SIZE = (80, 20)
DEFAULT_WINDOW_LOCATION = (None, None)
MAX_SCROLLED_TEXT_BOX_HEIGHT = 50
DEFAULT_TOOLTIP_TIME = 400
DEFAULT_TOOLTIP_OFFSET = (20,-20)
#################### COLOR STUFF ####################
BLUES = ("#082567", "#0A37A3", "#00345B")
PURPLES = ("#480656", "#4F2398", "#380474")
@ -194,6 +195,10 @@ TIMEOUT_KEY = '__TIMEOUT__'
# Key indicating should not create any return values for element
WRITE_ONLY_KEY = '__WRITE ONLY__'
MENU_DISABLED_CHARACTER = '!'
MENU_KEY_SEPARATOR = '::'
# 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):
@ -265,6 +270,7 @@ ELEM_TYPE_TABLE = 'table'
ELEM_TYPE_TREE = 'tree'
ELEM_TYPE_ERROR = 'error'
ELEM_TYPE_SEPARATOR = 'separator'
ELEM_TYPE_STATUSBAR = 'statusbar'
# ------------------------- Popup Buttons Types ------------------------- #
POPUP_BUTTONS_YES_NO = 1
@ -316,11 +322,13 @@ class ToolTip(object):
def showtip(self):
if self.tipwindow:
return
x = self.widget.winfo_rootx() + 20
y = self.widget.winfo_rooty() + self.widget.winfo_height() - 20
x = self.widget.winfo_rootx() + DEFAULT_TOOLTIP_OFFSET[0]
y = self.widget.winfo_rooty() + self.widget.winfo_height() + DEFAULT_TOOLTIP_OFFSET[1]
self.tipwindow = tk.Toplevel(self.widget)
self.tipwindow.wm_overrideredirect(True)
self.tipwindow.wm_geometry("+%d+%d" % (x, y))
self.tipwindow.wm_attributes("-topmost", 1)
label = tkinter.ttk.Label(self.tipwindow, text=self.text, justify=tk.LEFT,
background="#ffffe0", relief=tk.SOLID, borderwidth=1)
label.pack()
@ -489,7 +497,7 @@ class Element(object):
class InputText(Element):
def __init__(self, default_text='', size=(None, None), disabled=False, password_char='',
justification=None, background_color=None, text_color=None, font=None, tooltip=None,
change_submits=False,
change_submits=False, enable_events=False,
do_not_clear=False, key=None, focus=False, pad=None):
'''
Input a line of text Element
@ -506,11 +514,11 @@ class InputText(Element):
self.do_not_clear = do_not_clear
self.Justification = justification
self.Disabled = disabled
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
super().__init__(ELEM_TYPE_INPUT_TEXT, size=size, background_color=bg, text_color=fg, key=key, pad=pad,
font=font, tooltip=tooltip)
def Update(self, value=None, disabled=None):
def Update(self, value=None, disabled=None, select=None):
if disabled is True:
self.TKEntry['state'] = 'disabled'
elif disabled is False:
@ -521,6 +529,9 @@ class InputText(Element):
except:
pass
self.DefaultText = value
if select:
self.TKEntry.select_range(0, 'end')
def Get(self):
try:
@ -550,7 +561,7 @@ Input = InputText
# ---------------------------------------------------------------------- #
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,
text_color=None, change_submits=False, enable_events=False, disabled=False, key=None, pad=None, tooltip=None,
readonly=False, font=None):
'''
Input Combo Box Element (also called Dropdown box)
@ -561,7 +572,7 @@ class InputCombo(Element):
'''
self.Values = values
self.DefaultValue = default_value
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
self.TKCombo = None
# self.InitializeAsDisabled = disabled
self.Disabled = disabled
@ -682,9 +693,7 @@ InputOptionMenu = OptionMenu
# Listbox #
# ---------------------------------------------------------------------- #
class Listbox(Element):
def __init__(self, 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):
def __init__(self, values, default_values=None, select_mode=None, change_submits=False,enable_events=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):
'''
Listbox Element
:param values:
@ -705,7 +714,7 @@ class Listbox(Element):
self.Values = values
self.DefaultValues = default_values
self.TKListbox = None
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
self.BindReturnKey = bind_return_key
self.Disabled = disabled
if select_mode == LISTBOX_SELECT_MODE_BROWSE:
@ -724,7 +733,7 @@ class Listbox(Element):
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):
def Update(self, values=None, disabled=None, set_to_index=None):
if disabled == True:
self.TKListbox.configure(state='disabled')
elif disabled == False:
@ -735,6 +744,14 @@ class Listbox(Element):
self.TKListbox.insert(tk.END, item)
self.TKListbox.selection_set(0, 0)
self.Values = values
if set_to_index is not None:
self.TKListbox.selection_clear(0)
try:
self.TKListbox.selection_set(set_to_index, set_to_index)
except:
pass
def SetValue(self, values):
for index, item in enumerate(self.Values):
@ -763,7 +780,7 @@ class Listbox(Element):
# ---------------------------------------------------------------------- #
class Radio(Element):
def __init__(self, text, group_id, default=False, disabled=False, size=(None, None), auto_size_text=None,
background_color=None, text_color=None, font=None, key=None, pad=None, tooltip=None, change_submits=False):
background_color=None, text_color=None, font=None, key=None, pad=None, tooltip=None, change_submits=False, enable_events=False):
'''
Radio Button Element
:param text:
@ -787,7 +804,7 @@ class Radio(Element):
self.Value = None
self.Disabled = disabled
self.TextColor = text_color or DEFAULT_TEXT_COLOR
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
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,
@ -819,7 +836,7 @@ class Radio(Element):
# ---------------------------------------------------------------------- #
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, disabled=False, key=None, pad=None, tooltip=None):
text_color=None, change_submits=False,enable_events=False, disabled=False, key=None, pad=None, tooltip=None):
'''
Checkbox Element
:param text:
@ -841,7 +858,7 @@ class Checkbox(Element):
self.TKCheckbutton = None
self.Disabled = disabled
self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
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,
@ -879,7 +896,7 @@ Check = Checkbox
class Spin(Element):
# Values = None
# TKSpinBox = None
def __init__(self, values, initial_value=None, disabled=False, change_submits=False, size=(None, None),
def __init__(self, values, initial_value=None, disabled=False, change_submits=False,enable_events=False , size=(None, None),
auto_size_text=None, font=None, background_color=None, text_color=None, key=None, pad=None,
tooltip=None):
'''
@ -899,7 +916,7 @@ class Spin(Element):
'''
self.Values = values
self.DefaultValue = initial_value
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
self.TKSpinBox = None
self.Disabled = disabled
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
@ -950,8 +967,7 @@ class Spin(Element):
# ---------------------------------------------------------------------- #
class Multiline(Element):
def __init__(self, default_text='', enter_submits=False, disabled=False, autoscroll=False, size=(None, None),
auto_size_text=None, background_color=None, text_color=None, change_submits=False, do_not_clear=False, key=None, focus=False,
font=None, pad=None, tooltip=None):
auto_size_text=None, background_color=None, text_color=None, change_submits=False, enable_events=False,do_not_clear=False, key=None, focus=False, font=None, pad=None, tooltip=None):
'''
Multiline Element
:param default_text:
@ -962,13 +978,16 @@ class Multiline(Element):
:param auto_size_text:
:param background_color:
:param text_color:
:param change_submits:
:param enable_events:
:param do_not_clear:
:param key:
:param focus:
:param font:
:param pad:
:param tooltip:
:param font:
'''
self.DefaultText = default_text
self.EnterSubmits = enter_submits
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
@ -977,7 +996,7 @@ class Multiline(Element):
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
self.Autoscroll = autoscroll
self.Disabled = disabled
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
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, font=font or DEFAULT_FONT)
@ -1023,8 +1042,7 @@ class Multiline(Element):
# 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):
def __init__(self, text, size=(None, None), auto_size_text=None, click_submits=False, enable_events=False, relief=None, font=None, text_color=None, background_color=None, justification=None, pad=None, key=None, tooltip=None):
'''
Text Element
:param text:
@ -1044,7 +1062,7 @@ class Text(Element):
self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR
self.Justification = justification
self.Relief = relief
self.ClickSubmits = click_submits
self.ClickSubmits = click_submits or enable_events
if background_color is None:
bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR
else:
@ -1074,6 +1092,56 @@ Txt = Text
T = Text
# ---------------------------------------------------------------------- #
# StatusBar #
# ---------------------------------------------------------------------- #
class StatusBar(Element):
def __init__(self, text, size=(None, None), auto_size_text=None, click_submits=None, enable_events=False, relief=RELIEF_SUNKEN, font=None,
text_color=None, background_color=None, justification=None, pad=None, key=None, tooltip=None):
'''
Text Element
:param text:
:param size:
:param auto_size_text:
:param click_submits:
:param relief:
:param font:
:param text_color:
:param background_color:
:param justification:
:param pad:
:param key:
:param tooltip:
'''
self.DisplayText = text
self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR
self.Justification = justification
self.Relief = relief
self.ClickSubmits = click_submits or enable_events
if background_color is None:
bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR
else:
bg = background_color
super().__init__(ELEM_TYPE_STATUSBAR, size=size, auto_size_text=auto_size_text, background_color=bg, font=font or 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__()
# ---------------------------------------------------------------------- #
# TKProgressBar #
# Emulate the TK ProgressBar using canvas and rectangles
@ -1220,11 +1288,8 @@ class Output(Element):
def Update(self, value=None):
if value is not None:
# try:
self._TKOut.output.delete('1.0', tk.END)
self._TKOut.output.insert(tk.END, value)
# except:
# pass
def __del__(self):
@ -1240,7 +1305,7 @@ class Output(Element):
# ---------------------------------------------------------------------- #
class Button(Element):
def __init__(self, button_text='', button_type=BUTTON_TYPE_READ_FORM, target=(None, None), tooltip=None,
file_types=(("ALL Files", "*.*"),), initial_folder=None, disabled=False, change_submits=False, 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, font=None, bind_return_key=False, focus=False, pad=None, key=None):
file_types=(("ALL Files", "*.*"),), initial_folder=None, disabled=False, change_submits=False, enable_events=False, 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, font=None, bind_return_key=False, focus=False, pad=None, key=None):
'''
Button Element
:param button_text:
@ -1284,7 +1349,7 @@ class Button(Element):
self.DefaultDate_M_D_Y = (None, None, None)
self.InitialFolder = initial_folder
self.Disabled = disabled
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
super().__init__(ELEM_TYPE_BUTTON, size=size, font=font, pad=pad, key=key, tooltip=tooltip)
return
@ -1594,7 +1659,7 @@ class Canvas(Element):
# Graph #
# ---------------------------------------------------------------------- #
class Graph(Element):
def __init__(self, canvas_size, graph_bottom_left, graph_top_right, background_color=None, pad=None, change_submits=False, drag_submits=False, key=None,
def __init__(self, canvas_size, graph_bottom_left, graph_top_right, background_color=None, pad=None, change_submits=False, drag_submits=False, enable_events=False, key=None,
tooltip=None):
'''
Graph Element
@ -1611,7 +1676,7 @@ class Graph(Element):
self.TopRight = graph_top_right
self._TKCanvas = None
self._TKCanvas2 = None
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
self.DragSubmits = drag_submits
self.ClickPosition = (None, None)
self.MouseButtonDown = False
@ -1991,7 +2056,7 @@ class Tab(Element):
# ---------------------------------------------------------------------- #
class TabGroup(Element):
def __init__(self, layout, tab_location=None, title_color=None, selected_title_color=None, background_color=None,
font=None, change_submits=False, pad=None, border_width=None, theme=None, key=None, tooltip=None):
font=None, change_submits=False, enable_events=False,pad=None, border_width=None, theme=None, key=None, tooltip=None):
'''
TabGroup Element
:param layout:
@ -2020,7 +2085,7 @@ class TabGroup(Element):
self.BorderWidth = border_width
self.Theme = theme
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
self.TabLocation = tab_location
self.Layout(layout)
@ -2073,7 +2138,7 @@ class TabGroup(Element):
# ---------------------------------------------------------------------- #
class Slider(Element):
def __init__(self, range=(None, None), default_value=None, resolution=None, tick_interval=None, orientation=None,
border_width=None, relief=None, change_submits=False, disabled=False, size=(None, None), font=None,
border_width=None, relief=None, change_submits=False, enable_events=False, disabled=False, size=(None, None), font=None,
background_color=None, text_color=None, key=None, pad=None, tooltip=None):
'''
Slider Element
@ -2100,7 +2165,7 @@ class Slider(Element):
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
self.ChangeSubmits = change_submits or enable_events
self.Disabled = disabled
self.TickInterval = tick_interval
temp_size = size
@ -2523,17 +2588,42 @@ class Menu(Element):
self.MenuDefinition = menu_definition
self.TKMenu = None
self.Tearoff = tearoff
self.MenuItemChosen = None
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.MenuItemChosen = item_chosen
self.ParentForm.LastButtonClicked = item_chosen
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def Update(self, menu_definition):
self.MenuDefinition = menu_definition
self.TKMenu = tk.Menu(self.ParentForm.TKroot, tearoff=self.Tearoff) # create the menubar
menubar = self.TKMenu
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.ParentForm.TKroot.configure(menu=self.TKMenu)
def __del__(self):
super().__del__()
@ -2543,9 +2633,9 @@ class Menu(Element):
# ---------------------------------------------------------------------- #
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,
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,
size=(None, None), change_submits=False, bind_return_key=False, pad=None, key=None, tooltip=None):
size=(None, None), change_submits=False, enable_events=False, bind_return_key=False, pad=None, key=None, tooltip=None):
'''
Table Element
:param values:
@ -2557,11 +2647,17 @@ class Table(Element):
:param max_col_width:
:param select_mode:
:param display_row_numbers:
:param num_rows:
:param row_height:
:param font:
:param justification:
:param text_color:
:param background_color:
:param alternating_row_color:
:param size:
:param change_submits:
:param enable_events:
:param bind_return_key:
:param pad:
:param key:
:param tooltip:
@ -2580,10 +2676,11 @@ class Table(Element):
self.SelectMode = select_mode
self.DisplayRowNumbers = display_row_numbers
self.NumRows = num_rows if num_rows is not None else size[1]
self.RowHeight = row_height
self.TKTreeview = None
self.AlternatingRowColor = alternating_row_color
self.SelectedRows = []
self.ChangeSubmits = change_submits
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'
@ -2645,7 +2742,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, font=None,
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):
'''
@ -2683,7 +2780,7 @@ class Tree(Element):
self.Col0Width = col0_width
self.TKTreeview = None
self.SelectedRows = []
self.ChangeSubmits = change_submits
self.ChangeSubmits = change_submits or enable_events
super().__init__(ELEM_TYPE_TREE, text_color=text_color, background_color=background_color, font=font, pad=pad,
key=key, tooltip=tooltip)
@ -2890,6 +2987,7 @@ class Window(object):
self.DisableClose = disable_close
self._Hidden = False
self._Size = size
self.XFound = False
# ------------------------- Add ONE Row to Form ------------------------- #
def AddRow(self, *args):
@ -2915,12 +3013,13 @@ class Window(object):
return self
def LayoutAndRead(self, rows, non_blocking=False):
self.AddRows(rows)
self.Show(non_blocking=non_blocking)
return self.ReturnValues
raise DeprecationWarning('LayoutAndRead is no longer supported... change your call window.Layout(layout).Read()')
# 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')
raise DeprecationWarning('LayoutAndShow is no longer supported... ')
# ------------------------- ShowForm THIS IS IT! ------------------------- #
def Show(self, non_blocking=False):
@ -3036,7 +3135,7 @@ class Window(object):
except:
self.TKrootDestroyed = True
_my_windows.Decrement()
print('ROOT Destroyed')
# print('ROOT Destroyed')
results = BuildResults(self, False, self)
if results[0] != None and results[0] != timeout_key:
return results
@ -3088,6 +3187,12 @@ class Window(object):
self.LastButtonClicked = None
return results
else:
if not self.XFound and self.Timeout != 0 and self.Timeout is not None and self.ReturnValues[
0] is None: # Special Qt case because returning for no reason so fake timeout
self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout
elif not self.XFound and self.ReturnValues[0] is None: # TODO HIGHLY EXPERIMENTAL... added due to tray icon interaction
# print("*** Faking timeout ***")
self.ReturnValues = self.TimeoutKey, self.ReturnValues[1] # fake a timeout
return self.ReturnValues
def ReadNonBlocking(self):
@ -3096,7 +3201,8 @@ class Window(object):
self.TKroot.quit()
self.TKroot.destroy()
except:
print('DESTROY FAILED')
pass
# print('DESTROY FAILED')
return None, None
if not self.Shown:
self.Show(non_blocking=True)
@ -3105,10 +3211,10 @@ class Window(object):
except:
self.TKrootDestroyed = True
_my_windows.Decrement()
print("read failed")
# print("read failed")
# return None, None
if self.RootNeedsDestroying:
print('*** DESTROYING LATE ***', self.ReturnValues)
# print('*** DESTROYING LATE ***', self.ReturnValues)
self.TKroot.destroy()
_my_windows.Decrement()
self.Values = None
@ -3268,6 +3374,7 @@ class Window(object):
# print('Got closing callback', self.DisableClose)
if self.DisableClose:
return
self.XFound = True
if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit!
self.TKroot.quit() # kick the users out of the mainloop
self.TKroot.destroy() # kick the users out of the mainloop
@ -3375,45 +3482,45 @@ FlexForm = Window
# ------------------------- 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, disabled=False, change_submits=False, font=None, pad=None, key=None):
auto_size_button=None, button_color=None, disabled=False, change_submits=False, enable_events=False,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,
disabled=disabled, button_color=button_color,change_submits=change_submits, font=font, pad=pad, key=key)
disabled=disabled, button_color=button_color,change_submits=change_submits, enable_events=enable_events, 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, change_submits=False, font=None, disabled=False,
tooltip=None, size=(None, None), auto_size_button=None, button_color=None, change_submits=False, enable_events=False, font=None, disabled=False,
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, change_submits=change_submits, disabled=disabled, button_color=button_color, font=font, pad=pad, key=key)
initial_folder=initial_folder, tooltip=tooltip, size=size, auto_size_button=auto_size_button, change_submits=change_submits, enable_events=enable_events, disabled=disabled, 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", "*.*"),), disabled=False,
initial_folder=None, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, change_submits=False,
initial_folder=None, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, change_submits=False,enable_events=False,
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,change_submits=change_submits, tooltip=tooltip, size=size, auto_size_button=auto_size_button,
initial_folder=initial_folder,change_submits=change_submits, enable_events=enable_events, tooltip=tooltip, size=size, auto_size_button=auto_size_button,
disabled=disabled, 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,
disabled=False, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, change_submits=False, font=None,
disabled=False, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, change_submits=False, enable_events=False, 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, disabled=disabled,
auto_size_button=auto_size_button, button_color=button_color, change_submits=change_submits, font=font, pad=pad, key=key)
auto_size_button=auto_size_button, button_color=button_color, change_submits=change_submits, enable_events=enable_events, 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,
disabled=False, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, change_submits=False, font=None,
disabled=False, tooltip=None, size=(None, None), auto_size_button=None, button_color=None, change_submits=False, enable_events=False, 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, disabled=disabled,
auto_size_button=auto_size_button, button_color=button_color, change_submits=change_submits, font=font, pad=pad, key=key)
auto_size_button=auto_size_button, button_color=button_color, change_submits=change_submits, enable_events=enable_events,font=font, pad=pad, key=key)
# ------------------------- SAVE BUTTON Element lazy function ------------------------- #
@ -3766,6 +3873,11 @@ def BuildResultsForSubform(form, initialize_only, top_level_form):
value = element.SelectedRows
elif element.Type == ELEM_TYPE_GRAPH:
value = element.ClickPosition
elif element.Type == ELEM_TYPE_MENUBAR:
if element.MenuItemChosen is not None:
button_pressed_text = top_level_form.LastButtonClicked = element.MenuItemChosen
value = element.MenuItemChosen
element.MenuItemChosen = None
else:
value = None
@ -3919,7 +4031,17 @@ if sys.version_info[0] >= 3:
if sub_menu_info == '---':
top_menu.add('separator')
else:
top_menu.add_command(label=sub_menu_info, underline=pos,
try:
item_without_key = sub_menu_info[:sub_menu_info.index(MENU_KEY_SEPARATOR)]
except:
item_without_key = sub_menu_info
if item_without_key[0] == MENU_DISABLED_CHARACTER:
top_menu.add_command(label=item_without_key[len(MENU_DISABLED_CHARACTER):], underline=pos,
command=lambda: Menu.MenuItemChosenCallback(element, sub_menu_info))
top_menu.entryconfig(item_without_key[len(MENU_DISABLED_CHARACTER):], state='disabled')
else:
top_menu.add_command(label=item_without_key, underline=pos,
command=lambda: Menu.MenuItemChosenCallback(element, sub_menu_info))
else:
i = 0
@ -3932,7 +4054,10 @@ if sys.version_info[0] >= 3:
if pos != -1:
if pos == 0 or sub_menu_info[i][pos - 1] != "\\":
sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1:]
top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu, underline=pos)
if sub_menu_info[i][0] == MENU_DISABLED_CHARACTER:
top_menu.add_cascade(label=sub_menu_info[i][len(MENU_DISABLED_CHARACTER):], menu=new_menu, underline=pos, state='disabled')
else:
top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu, underline=pos)
AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True)
i += 1 # skip the next one
else:
@ -3952,7 +4077,17 @@ else:
if sub_menu_info == '---':
top_menu.add('separator')
else:
top_menu.add_command(label=sub_menu_info, underline=pos,
try:
item_without_key = sub_menu_info[:sub_menu_info.index(MENU_KEY_SEPARATOR)]
except:
item_without_key = sub_menu_info
if item_without_key[0] == MENU_DISABLED_CHARACTER:
top_menu.add_command(label=item_without_key[len(MENU_DISABLED_CHARACTER):], underline=pos,
command=lambda: Menu.MenuItemChosenCallback(element, sub_menu_info))
top_menu.entryconfig(item_without_key[len(MENU_DISABLED_CHARACTER):], state='disabled')
else:
top_menu.add_command(label=item_without_key, underline=pos,
command=lambda: Menu.MenuItemChosenCallback(element, sub_menu_info))
else:
i = 0
@ -3965,7 +4100,10 @@ else:
if pos != -1:
if pos == 0 or sub_menu_info[i][pos - 1] != "\\":
sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + 1:]
top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu, underline=pos)
if sub_menu_info[i][0] == MENU_DISABLED_CHARACTER:
top_menu.add_cascade(label=sub_menu_info[i][len(MENU_DISABLED_CHARACTER):], menu=new_menu, underline=pos, state='disabled')
else:
top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu, underline=pos)
AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True)
i += 1 # skip the next one
else:
@ -4528,7 +4666,12 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form):
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 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], element)
toplevel_form.TKroot.configure(menu=element.TKMenu)
@ -4723,6 +4866,8 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form):
fieldbackground=element.BackgroundColor)
if element.TextColor is not None and element.TextColor != COLOR_SYSTEM_DEFAULT:
tkinter.ttk.Style().configure("Treeview", foreground=element.TextColor)
if element.RowHeight is not None:
tkinter.ttk.Style().configure("Treeview", rowheight=element.RowHeight)
# scrollable_frame.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], expand=True, fill='both')
treeview.bind("<<TreeviewSelect>>", element.treeview_selected)
if element.BindReturnKey:
@ -4804,6 +4949,57 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form):
elif element_type == ELEM_TYPE_SEPARATOR:
separator = tkinter.ttk.Separator(tk_row_frame, orient=element.Orientation, )
separator.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1], fill='both', expand=True)
# ------------------------- StatusBar element ------------------------- #
elif element_type == ELEM_TYPE_STATUSBAR:
# 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)
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],fill=tk.BOTH, expand=True)
element.TKText = tktext_label
if element.ClickSubmits:
tktext_label.bind('<Button-1>', element.TextClickedHandler)
if element.Tooltip is not None:
element.TooltipObject = ToolTip(element.TKText, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME)
# ............................DONE WITH ROW pack the row of widgets ..........................#
# done with row, pack the row of widgets
@ -5382,6 +5578,8 @@ def EasyPrintClose():
# ======================== Scrolled Text Box =====#
# ===================================================#
def PopupScrolled(*args, **_3to2kwargs):
if 'location' in _3to2kwargs: location = _3to2kwargs['location']; del _3to2kwargs['location']
else: location = (None, None)
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']
@ -5396,7 +5594,7 @@ def PopupScrolled(*args, **_3to2kwargs):
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)
auto_close_duration=auto_close_duration, location=location)
max_line_total, max_line_width, total_lines, height_computed = 0, 0, 0, 0
complete_output = ''
for message in args:
@ -6874,7 +7072,7 @@ def main():
[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()],
FolderBrowse(tooltip='Browse for a folder')],
[Text('Destination Folder', size=(15, 1), justification='right'), InputText('Dest'), FolderBrowse()],
[Ok(), Cancel()]]

View File

@ -26,8 +26,9 @@
# NEW for NOV 2018 - Run Qt using PySimpleGUI!
## Supports both Python 2.7 & 3 when using tkinter
## Supports both PySide2 and PyQt5
## PySimpleGUI source code can run either on Qt or tkinter with no changes
## Supports both PySide2 and PyQt5 (limited support)
## PySimpleGUI source code can run either on Qt or tkinter by changing only the import
![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.17.0-red.svg?longCache=true&style=for-the-badge)
@ -280,7 +281,9 @@ Maybe there's no "there there". ***Or*** maybe a simple GUI API will enable Pyt
-----
## Getting Started with PySimpleGUI
# Getting Started with PySimpleGUI
## Installing PySimpleGUI
### Installing Python 3
@ -380,6 +383,12 @@ PySimpleGUI Runs on all Python3 platforms that have tkinter running on them. It
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
## Qt Version
Please see the Qt specific documentation. The readme that contains all you need to know about Qt specific matters is here:
https://pysimplegui.readthedocs.io/readmeqt/
## Using - Python 3
To use in your code, simply import....
@ -404,7 +413,7 @@ Those using Python 2.7 will import a different module name
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
# APIs
PySimpleGUI can be broken down into 2 types of API's:
* High Level single call functions (The `Popup` calls)
@ -461,7 +470,7 @@ Dictionaries are used by more advanced PySimpleGUI users. You'll know that dict
---
## High Level API Calls - Popup's
# 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.
@ -698,7 +707,7 @@ This is a typpical call
## Progress Meters!
# 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?
@ -727,7 +736,7 @@ With a little trickery you can provide a way to break out of your loop using the
***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
# 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
@ -1006,7 +1015,7 @@ You don't HAVE to write your reads in this way. You can name your variables howe
## Events
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:
The first parameter `event` describes **why** the read completed. Events are one of these:
For all Windows:
@ -1048,15 +1057,17 @@ while True:
By default buttons will always return a click event, or in the case of realtime buttons, a button down event. You don't have to do anything to enable button clicks. To disable the events, disable the button using its Update method.
You can enable an additional "Button Modified" event by setting `enable_events=True` in the Button call. These events are triggered when something 'writes' to a button, ***usually*** it's because the button is listed as a "target" in another button.
The button value from a Read call will be one of 2 values:
1. The Button's text
2. The Button's key
1. The Button's text - Default
2. The Button's key - If a key is specified
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.
If a button has a key set when it was 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 event value is None.
None is returned when the user clicks the X to close a window.
### **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:
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()
@ -1097,7 +1108,7 @@ Windows are capable of returning keyboard events. These are returned as either
If you set a timeout parameter in your read, then the system TIMEOUT_KEY will be returned. If you specified your own timeout key in the Read call then that value will be what's returned instead.
### The 'values' Variable - Return values as a list
### 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.
@ -1120,9 +1131,9 @@ However, this method isn't good when you have a lot of input fields. If you ins
The more common / advanced method is to request your values be returned as a dictionary.
### Return values as a dictionary
### `values` Variable - Return values as a dictionary
For those of you that have not encountered a Python dictionary, don't freak out! Just copy and paste this code and modify it. Follow this design pattern and you'll be fine. And you might learn something along the way.
For those of you that have not encountered a Python dictionary, don't freak out! Just copy and paste the sample code and modify it. Follow this design pattern and you'll be fine. And you might learn something along the way.
For windows longer than 3 or 4 fields you will want to use a dictionary to help you organize your return values. In almost all (if not all) of the demo programs you'll find the return values being passed as a dictionary. It is not a difficult concept to grasp, the syntax is easy to understand, and it makes for very readable code.
@ -1437,12 +1448,13 @@ 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.
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.
#### Read(timeout=None, timeout_key='__timeout_ _ ')
#### Read(timeout=None, timeout_key='__TIMEOUT_ _ ')
Read the Window's input values and button clicks in a blocking-fashion
Returns event, values. Adding a timeout can be achieved by setting timeout=number of milliseconds before the Read times out after which a "timeout event" is returned. The value of timeout_key will be returned as the event.
Returns event, values. Adding a timeout can be achieved by setting timeout=number of milliseconds before the Read times out after which a "timeout event" is returned. The value of timeout_key will be returned as the event. If you do not specify a timeout key, then the value `TIMEOUT_KEY` will be returned.
If you set the timeout = 0, then the Read will immediately return rather than waiting for input or for a timeout. This is the same as the old ReadNonBlocking call.
@ -1450,7 +1462,7 @@ If you set the timeout = 0, then the Read will immediately return rather than wa
While this call will technically still work, it is being removed. If you want to get the same result, call Read with timeout = 0.
Read the Window's input values and button clicks but without blocking. It will immediately return. **Consider using Read with timeout instead!**
Read the Window's input values and button clicks but without blocking. It will immediately return. **Consider using Read with non-zero timeout instead!**
Will consume 100% of your CPU if you do not have other blocking calls in your event loop.
@ -1484,6 +1496,9 @@ Fills in a window's fields based on previously saved file
Returns the size (w,h) of the screen in pixels
#### CurrentLocation()
Returns current screen position (x,y)
#### Move(x, y)
Move window to (x,y) position on the screen
@ -2820,26 +2835,62 @@ 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.
Out of all of the Elements, it's the Table and the Tree that are the most "problematic" in the tkinter inter and Qt implementations. They're hard is my only defense.
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
### Known visualization problem....
If you click on the header, it can go into spasms for some tables. I don't understand what's causing it and it's been there evidently since the first release of Tables.
```python
Table( 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,
size=(None,None),
change_submits=False,
enable_events=False,
bind_return_key=False,
pad=None,
key=None,
tooltip=None):
```
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
num_rows = the number of rows to display at a time (same as size[0])
row_height = number of pixels high a row should be. Normally left as default value
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
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
change_submits - the old way of indicating enable_events
bind_return_key - returns event if a double click or a return key is pressed while row is highlighted
pad - element padding for packing
key - key used to lookup element
tooltip - tooltip text
### Read return values from Table Element
@ -3428,7 +3479,8 @@ Use realtime keyboard capture by calling
# 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.
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 events and as of 3.17, also as values. The value returned will be the entire menu entry, including the key if you specified one.
This definition:
@ -3443,6 +3495,7 @@ They menu_def layout produced this window:
![menu](https://user-images.githubusercontent.com/13696193/45306723-56b7cb00-b4eb-11e8-8cbd-faef0c90f8b4.jpg)
## Menu Shortcut keys
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.
@ -3463,10 +3516,30 @@ menu_def = [['&File', ['&Open', '&Save', '---', 'Properties', 'E&xit' ]],
![menus with shortcuts](https://user-images.githubusercontent.com/13696193/46251674-f5b74f00-c427-11e8-95c6-547adc59041b.jpg)
## Disabled Menu Entries
If you want one of your menu items to be disabled, then place a '!' in front of the menu entry. To disable the Paste menu entry in the previous examples, the entry would be:
`['!&Edit', ['Paste', ['Special', 'Normal',], 'Undo'],]`
If your want to change the disabled menu item flag / character from '!' to something else, change the variable `MENU_DISABLED_CHARACTER`
## Keys for Menus
Beginning in version 3.17 you can add a `key` to your menu entries. The `key` value will be removed prior to be inserted into the menu. When you receive Menu events, the entire menu entry, including the `key` is returned. A key is indicated by adding `::` after a menu entry, followed by the key.
To add the `key` `_MY_KEY_` to the Special menu entry, the code would be:
`['&Edit', ['Paste', ['Special::_MY_KEY_', 'Normal',], 'Undo'],]`
If you want to change the characters that indicate a key follows from '::' to something else, change the variable `MENU_KEY_SEPARATOR`
# Sample Applications
There are too many to list!!
There are over 130 sample programs to give you a jump start.
Use the example programs as a starting basis for your GUI. Copy, paste, modify and run! The demo files are:
| Source File| Description |
@ -3986,12 +4059,11 @@ New `Element` shortcut function for `FindElement`
Dummy Stretch Element made for backwards compatibility with Qt
Timer function prints in milliseconds now, was seconds
### 3.17.0 &1.17.0 1-Dec-2018
### 3.17.0 &1.17.0 2-Dec-2018
Tooltip offset now programmable. Set variable DEFAULT_TOOLTIP_OFFSET. Defaults to (20,-20)
Tooltips are always on top now
Disable menu items
Menu items can have keys
StatusBar Element (preparing for a real status bar in Qt)
enable_events parameter added to ALL Elements capable of generating events
select parameter to InputText.Update will select the input text
Listbox.Update - set_to_index parameter will select a single items
@ -4000,7 +4072,10 @@ Menus have an entry in the return values
LayoutAndRead depricated
Multi-window support continues (X detection)
PopupScrolled now has a location parameter
row_height parameter to Table Element
Stretch Element (DUMMY) so that can be source code compatible with Qt
ButtonMenu Element (DUMMY) so can be source code compatible with Qt. Will implement eventually
StatusBar Element (preparing for a real status bar in Qt) based on Text Element
### Upcoming
Make suggestions people! Future release features

161
readme.md
View File

@ -26,8 +26,9 @@
# NEW for NOV 2018 - Run Qt using PySimpleGUI!
## Supports both Python 2.7 & 3 when using tkinter
## Supports both PySide2 and PyQt5
## PySimpleGUI source code can run either on Qt or tkinter with no changes
## Supports both PySide2 and PyQt5 (limited support)
## PySimpleGUI source code can run either on Qt or tkinter by changing only the import
![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.17.0-red.svg?longCache=true&style=for-the-badge)
@ -280,7 +281,9 @@ Maybe there's no "there there". ***Or*** maybe a simple GUI API will enable Pyt
-----
## Getting Started with PySimpleGUI
# Getting Started with PySimpleGUI
## Installing PySimpleGUI
### Installing Python 3
@ -380,6 +383,12 @@ PySimpleGUI Runs on all Python3 platforms that have tkinter running on them. It
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
## Qt Version
Please see the Qt specific documentation. The readme that contains all you need to know about Qt specific matters is here:
https://pysimplegui.readthedocs.io/readmeqt/
## Using - Python 3
To use in your code, simply import....
@ -404,7 +413,7 @@ Those using Python 2.7 will import a different module name
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
# APIs
PySimpleGUI can be broken down into 2 types of API's:
* High Level single call functions (The `Popup` calls)
@ -461,7 +470,7 @@ Dictionaries are used by more advanced PySimpleGUI users. You'll know that dict
---
## High Level API Calls - Popup's
# 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.
@ -698,7 +707,7 @@ This is a typpical call
## Progress Meters!
# 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?
@ -727,7 +736,7 @@ With a little trickery you can provide a way to break out of your loop using the
***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
# 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
@ -1006,7 +1015,7 @@ You don't HAVE to write your reads in this way. You can name your variables howe
## Events
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:
The first parameter `event` describes **why** the read completed. Events are one of these:
For all Windows:
@ -1048,15 +1057,17 @@ while True:
By default buttons will always return a click event, or in the case of realtime buttons, a button down event. You don't have to do anything to enable button clicks. To disable the events, disable the button using its Update method.
You can enable an additional "Button Modified" event by setting `enable_events=True` in the Button call. These events are triggered when something 'writes' to a button, ***usually*** it's because the button is listed as a "target" in another button.
The button value from a Read call will be one of 2 values:
1. The Button's text
2. The Button's key
1. The Button's text - Default
2. The Button's key - If a key is specified
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.
If a button has a key set when it was 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 event value is None.
None is returned when the user clicks the X to close a window.
### **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:
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()
@ -1097,7 +1108,7 @@ Windows are capable of returning keyboard events. These are returned as either
If you set a timeout parameter in your read, then the system TIMEOUT_KEY will be returned. If you specified your own timeout key in the Read call then that value will be what's returned instead.
### The 'values' Variable - Return values as a list
### 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.
@ -1120,9 +1131,9 @@ However, this method isn't good when you have a lot of input fields. If you ins
The more common / advanced method is to request your values be returned as a dictionary.
### Return values as a dictionary
### `values` Variable - Return values as a dictionary
For those of you that have not encountered a Python dictionary, don't freak out! Just copy and paste this code and modify it. Follow this design pattern and you'll be fine. And you might learn something along the way.
For those of you that have not encountered a Python dictionary, don't freak out! Just copy and paste the sample code and modify it. Follow this design pattern and you'll be fine. And you might learn something along the way.
For windows longer than 3 or 4 fields you will want to use a dictionary to help you organize your return values. In almost all (if not all) of the demo programs you'll find the return values being passed as a dictionary. It is not a difficult concept to grasp, the syntax is easy to understand, and it makes for very readable code.
@ -1437,12 +1448,13 @@ 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.
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.
#### Read(timeout=None, timeout_key='__timeout_ _ ')
#### Read(timeout=None, timeout_key='__TIMEOUT_ _ ')
Read the Window's input values and button clicks in a blocking-fashion
Returns event, values. Adding a timeout can be achieved by setting timeout=number of milliseconds before the Read times out after which a "timeout event" is returned. The value of timeout_key will be returned as the event.
Returns event, values. Adding a timeout can be achieved by setting timeout=number of milliseconds before the Read times out after which a "timeout event" is returned. The value of timeout_key will be returned as the event. If you do not specify a timeout key, then the value `TIMEOUT_KEY` will be returned.
If you set the timeout = 0, then the Read will immediately return rather than waiting for input or for a timeout. This is the same as the old ReadNonBlocking call.
@ -1450,7 +1462,7 @@ If you set the timeout = 0, then the Read will immediately return rather than wa
While this call will technically still work, it is being removed. If you want to get the same result, call Read with timeout = 0.
Read the Window's input values and button clicks but without blocking. It will immediately return. **Consider using Read with timeout instead!**
Read the Window's input values and button clicks but without blocking. It will immediately return. **Consider using Read with non-zero timeout instead!**
Will consume 100% of your CPU if you do not have other blocking calls in your event loop.
@ -1484,6 +1496,9 @@ Fills in a window's fields based on previously saved file
Returns the size (w,h) of the screen in pixels
#### CurrentLocation()
Returns current screen position (x,y)
#### Move(x, y)
Move window to (x,y) position on the screen
@ -2820,26 +2835,62 @@ 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.
Out of all of the Elements, it's the Table and the Tree that are the most "problematic" in the tkinter inter and Qt implementations. They're hard is my only defense.
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
### Known visualization problem....
If you click on the header, it can go into spasms for some tables. I don't understand what's causing it and it's been there evidently since the first release of Tables.
```python
Table( 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,
size=(None,None),
change_submits=False,
enable_events=False,
bind_return_key=False,
pad=None,
key=None,
tooltip=None):
```
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
num_rows = the number of rows to display at a time (same as size[0])
row_height = number of pixels high a row should be. Normally left as default value
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
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
change_submits - the old way of indicating enable_events
bind_return_key - returns event if a double click or a return key is pressed while row is highlighted
pad - element padding for packing
key - key used to lookup element
tooltip - tooltip text
### Read return values from Table Element
@ -3428,7 +3479,8 @@ Use realtime keyboard capture by calling
# 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.
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 events and as of 3.17, also as values. The value returned will be the entire menu entry, including the key if you specified one.
This definition:
@ -3443,6 +3495,7 @@ They menu_def layout produced this window:
![menu](https://user-images.githubusercontent.com/13696193/45306723-56b7cb00-b4eb-11e8-8cbd-faef0c90f8b4.jpg)
## Menu Shortcut keys
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.
@ -3463,10 +3516,30 @@ menu_def = [['&File', ['&Open', '&Save', '---', 'Properties', 'E&xit' ]],
![menus with shortcuts](https://user-images.githubusercontent.com/13696193/46251674-f5b74f00-c427-11e8-95c6-547adc59041b.jpg)
## Disabled Menu Entries
If you want one of your menu items to be disabled, then place a '!' in front of the menu entry. To disable the Paste menu entry in the previous examples, the entry would be:
`['!&Edit', ['Paste', ['Special', 'Normal',], 'Undo'],]`
If your want to change the disabled menu item flag / character from '!' to something else, change the variable `MENU_DISABLED_CHARACTER`
## Keys for Menus
Beginning in version 3.17 you can add a `key` to your menu entries. The `key` value will be removed prior to be inserted into the menu. When you receive Menu events, the entire menu entry, including the `key` is returned. A key is indicated by adding `::` after a menu entry, followed by the key.
To add the `key` `_MY_KEY_` to the Special menu entry, the code would be:
`['&Edit', ['Paste', ['Special::_MY_KEY_', 'Normal',], 'Undo'],]`
If you want to change the characters that indicate a key follows from '::' to something else, change the variable `MENU_KEY_SEPARATOR`
# Sample Applications
There are too many to list!!
There are over 130 sample programs to give you a jump start.
Use the example programs as a starting basis for your GUI. Copy, paste, modify and run! The demo files are:
| Source File| Description |
@ -3986,12 +4059,11 @@ New `Element` shortcut function for `FindElement`
Dummy Stretch Element made for backwards compatibility with Qt
Timer function prints in milliseconds now, was seconds
### 3.17.0 &1.17.0 1-Dec-2018
### 3.17.0 &1.17.0 2-Dec-2018
Tooltip offset now programmable. Set variable DEFAULT_TOOLTIP_OFFSET. Defaults to (20,-20)
Tooltips are always on top now
Disable menu items
Menu items can have keys
StatusBar Element (preparing for a real status bar in Qt)
enable_events parameter added to ALL Elements capable of generating events
select parameter to InputText.Update will select the input text
Listbox.Update - set_to_index parameter will select a single items
@ -4000,7 +4072,10 @@ Menus have an entry in the return values
LayoutAndRead depricated
Multi-window support continues (X detection)
PopupScrolled now has a location parameter
row_height parameter to Table Element
Stretch Element (DUMMY) so that can be source code compatible with Qt
ButtonMenu Element (DUMMY) so can be source code compatible with Qt. Will implement eventually
StatusBar Element (preparing for a real status bar in Qt) based on Text Element
### Upcoming
Make suggestions people! Future release features