2.3 Release

Large change to ReadMe and Recipes.  Some functions renamed or a new name was created, leaving legacy name in place... for now.  As long as docs steer people in the direction of the new names it'll be ok
This commit is contained in:
MikeTheWatchGuy 2018-07-23 15:14:34 -04:00
parent f6f02b6f2b
commit f3bee1687e
3 changed files with 626 additions and 371 deletions

View File

@ -4,7 +4,7 @@ import random
import string import string
import PySimpleGUI as SG import PySimpleGUI as SG
# A simple blocking form. Your best starter-form
def SourceDestFolders(): def SourceDestFolders():
with SG.FlexForm('Demo Source / Destination Folders', auto_size_text=True) as form: with SG.FlexForm('Demo Source / Destination Folders', auto_size_text=True) as form:
form_rows = [[SG.Text('Enter the Source and Destination folders')], form_rows = [[SG.Text('Enter the Source and Destination folders')],
@ -12,161 +12,158 @@ def SourceDestFolders():
[SG.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Dest'), SG.FolderBrowse()], [SG.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Dest'), SG.FolderBrowse()],
[SG.Submit(), SG.Cancel()]] [SG.Submit(), SG.Cancel()]]
button, (source, dest) = form.LayoutAndShow(form_rows) button, (source, dest) = form.LayoutAndRead(form_rows)
if button == 'Submit': if button == 'Submit':
# do something useful with the inputs
SG.MsgBox('Submitted', 'The user entered source:', source, 'Destination folder:', dest, 'Using button', button) SG.MsgBox('Submitted', 'The user entered source:', source, 'Destination folder:', dest, 'Using button', button)
else: else:
SG.MsgBoxError('Cancelled', 'User Cancelled') SG.MsgBoxError('Cancelled', 'User Cancelled')
# YOUR BEST STARTING POINT
# This is a form showing you all of the basic Elements (widgets)
# Some have a few of the optional parameters set, but there are more to choose from
# You want to use the context manager because it will free up resources when you are finished
# Use this especially if you are runningm multi-threaded
# Where you free up resources is really important to tkinter
def Everything():
with SG.FlexForm('Everything bagel', auto_size_text=True, default_element_size=(40, 1)) as form:
layout = [
[SG.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25), text_color='blue')],
[SG.Text('Here is some text.... and a place to enter text')],
[SG.InputText()],
[SG.Checkbox('My first checkbox!'), SG.Checkbox('My second checkbox!', default=True)],
[SG.Radio('My first Radio! ', "RADIO1", default=True), SG.Radio('My second Radio!', "RADIO1")],
[SG.Multiline(default_text='This is the default Text shoulsd you decide not to type anything',
scale=(2, 10))],
[SG.InputCombo(['Combobox 1', 'Combobox 2'], size=(20, 3)),
SG.Slider(range=(1, 100), orientation='h', size=(35, 20), default_value=85)],
[SG.Listbox(values=['Listbox 1', 'Listbox 2', 'Listbox 3'], size=(30, 6)),
SG.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=25),
SG.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=75),
SG.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=10)],
[SG.Text('_' * 100, size=(70, 1))],
[SG.Text('Choose Source and Destination Folders', size=(35, 1))],
[SG.Text('Source Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Source'), SG.FolderBrowse()],
[SG.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Dest'),
SG.FolderBrowse()],
[SG.Submit(), SG.Cancel(), SG.SimpleButton('Customized', button_color=('white', 'green'))]
]
button, values = form.LayoutAndRead(layout)
SG.MsgBox('Title', 'Typical message box', 'The results of the form are a lot of data! Get ready... ', 'The button clicked was "{}"'.format(button), 'The values are', values)
# Should you decide not to use a context manager, then try this form as your starting point
# Be aware that tkinter, which this is based on, is picky about who frees up resources, especially if
# you are running multithreaded
def Everything_NoContextManager(): def Everything_NoContextManager():
form = SG.FlexForm('Everything bagel', auto_size_text=True, default_element_size=(40, 1)) form = SG.FlexForm('Everything bagel', auto_size_text=True, default_element_size=(40, 1))
layout = [[SG.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25), text_color='blue')], layout = [[SG.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25), text_color='blue')],
[SG.Text('Here is some text.... and a place to enter text')], [SG.Text('Here is some text.... and a place to enter text')],
[SG.InputText()], [SG.InputText()],
[SG.Checkbox('My first checkbox!'), SG.Checkbox('My second checkbox!', default=True)], [SG.Checkbox('My first checkbox!'), SG.Checkbox('My second checkbox!', default=True)],
[SG.Radio('My first Radio!', "RADIO1", default=True), SG.Radio('My second Radio!', "RADIO1")], [SG.Radio('My first Radio! ', "RADIO1", default=True), SG.Radio('My second Radio!', "RADIO1")],
[SG.Multiline(default_text='This is the default Text should you decide not to type anything', scale=(2, 10))], [SG.Multiline(default_text='This is the default Text shoulsd you decide not to type anything', scale=(2, 10))],
[SG.InputCombo(['choice 1', 'choice 2'], size=(20, 3))], [SG.InputCombo(['Combobox 1', 'Combobox 2'], size=(20, 3)), SG.Slider(range=(1,100), orientation='h', size=(35,20), default_value=85)],
[SG.Listbox(values=['Listbox 1','Listbox 2', 'Listbox 3'], size=(30,6)),
SG.Slider(range=(1,100), orientation='v', size=(10, 20), default_value=25),
SG.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=75),
SG.Slider(range=(1, 100), orientation='v', size=(10, 20), default_value=10)],
[SG.Text('_' * 100, size=(70, 1))], [SG.Text('_' * 100, size=(70, 1))],
[SG.Text('Choose Source and Destination Folders', size=(35, 1))], [SG.Text('Choose Source and Destination Folders', size=(35, 1), text_color='red')],
[SG.Text('Source Folder', size=(15, 1), auto_size_text=False), SG.InputText('Source'), SG.FolderBrowse()], [SG.Text('Source Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Source'), SG.FolderBrowse()],
[SG.Text('Destination Folder', size=(15, 1), auto_size_text=False), SG.InputText('Dest'), SG.FolderBrowse()], [SG.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Dest'), SG.FolderBrowse()],
[SG.SimpleButton('Your very own button', button_color=('white', 'green'))], [SG.Submit(), SG.Cancel(), SG.SimpleButton('Customized', button_color=('white', 'green'))]]
[SG.Submit(), SG.Cancel()]]
button, (values) = form.LayoutAndShow(layout) button, values = form.LayoutAndRead(layout)
del(form)
SG.MsgBox('Title', 'Typical message box', 'Here are the restults! There is one entry per input field ', 'The button clicked was "{}"'.format(button), 'The values are', values) SG.MsgBox('Title', 'Typical message box', 'Here are the restults! There is one entry per input field ', 'The button clicked was "{}"'.format(button), 'The values are', values)
def Everything():
with SG.FlexForm('Everything bagel', auto_size_text=True, default_element_size=(40, 1)) as form:
layout = [[SG.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25), text_color='blue')],
[SG.Text('Here is some text.... and a place to enter text')],
[SG.InputText()],
[SG.Checkbox('My first checkbox!'), SG.Checkbox('My second checkbox!', default=True)],
[SG.Radio('My first Radio!', "RADIO1", default=True), SG.Radio('My second Radio!', "RADIO1")],
[SG.Spin(values=(1,2,3), initial_value=1, size=(2,1)), SG.T('Spinner 1', size=(20,1)),
SG.Spin(values=(1,2,3), initial_value=1, size=(2,1)),SG.T('Spinner 2')],
[SG.Multiline(default_text='This is the default Text should you decide not to type anything', scale=(2, 10))],
[SG.InputCombo(['choice 1', 'choice 2'], size=(20, 3))],
[SG.Text('_' * 100, size=(70, 1))],
[SG.Text('Choose Source and Destination Folders', size=(35, 1))],
[SG.Text('Source Folder', size=(15, 1), auto_size_text=False), SG.InputText('Source'), SG.FolderBrowse()],
[SG.Text('Destination Folder', size=(15, 1), auto_size_text=False), SG.InputText('Dest'), SG.FolderBrowse()],
[SG.SimpleButton('Custom Button', button_color=('white', 'green'))],
[SG.Submit(), SG.Cancel()]]
button, (values) = form.LayoutAndShow(layout)
SG.MsgBox('Title', 'Typical message box', 'The results of the form are a lot of data! Get ready... ', 'The button clicked was "{}"'.format(button), 'The values are', values)
def ProgressMeter(): def ProgressMeter():
for i in range(1,10000): for i in range(1,10000):
if not SG.EasyProgressMeter('My Meter', i+1, 10000): break if not SG.EasyProgressMeter('My Meter', i+1, 10000): break
# SG.Print(i)
def RunningTimer(): # Blocking form that doesn't close
with SG.FlexForm('Running Timer', auto_size_text=True) as form:
output_element = SG.Text('', size=(8, 2), font=('Helvetica', 20))
form_rows = [[SG.Text('Non-blocking GUI with updates')],
[output_element],
[SG.SimpleButton('Quit')]]
form.AddRows(form_rows)
form.Show(non_blocking=True)
for i in range(1, 100):
output_element.Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i/100), 60), i%100))
rc = form.Refresh()
if rc is None or rc[0] == 'Quit':
break
time.sleep(.01)
else:
form.CloseNonBlockingForm()
# Persistant form. Does not close when Send button is clicked.
# Normally all Simple Buttons cause forms to close
def ChatBot(): def ChatBot():
with SG.FlexForm('Chat Window', auto_size_text=True, default_element_size=(30, 2)) as form: with SG.FlexForm('Chat Window', auto_size_text=True, default_element_size=(30, 2)) as form:
form.AddRow(SG.Text('This is where standard out is being routed', size=[40, 1])) layout = [[(SG.Text('This is where standard out is being routed', size=[40, 1]))],
form.AddRow(SG.Output(size=(80, 20))) [SG.Output(size=(80, 20))],
form.AddRow(SG.Multiline(size=(70, 5), enter_submits=True), SG.ReadFormButton('SEND', button_color=(SG.YELLOWS[0], SG.BLUES[0])), SG.SimpleButton('EXIT', button_color=(SG.YELLOWS[0], SG.GREENS[0]))) [SG.Multiline(size=(70, 5), enter_submits=True), SG.ReadFormButton('SEND', button_color=(SG.YELLOWS[0], SG.BLUES[0])), SG.SimpleButton('EXIT', button_color=(SG.YELLOWS[0], SG.GREENS[0]))]]
button, value = form.Read() # notice this is NOT the usual LayoutAndRead call because you don't yet want to read the form
# if you call LayoutAndRead from here, then you will miss the first button click
form.Layout(layout)
# ---===--- Loop taking in user input and using it to query HowDoI web oracle --- # # ---===--- Loop taking in user input and using it to query HowDoI web oracle --- #
while True: while True:
button, value = form.Read()
if button == 'SEND': if button == 'SEND':
print(value) print(value)
else: else:
break break
button, value = form.Read()
# Shows a form that's a running counter
# this is the basic design pattern if you can keep your reading of the
# form within the 'with' block. If your read occurs far away in your code from the form creation
# then you will want to use the NonBlockingPeriodicUpdateForm example
def NonBlockingPeriodicUpdateForm_ContextManager(): def NonBlockingPeriodicUpdateForm_ContextManager():
# Show a form that's a running counter
with SG.FlexForm('Running Timer', auto_size_text=True) as form: with SG.FlexForm('Running Timer', auto_size_text=True) as form:
output_element = SG.Text('',size=(10,2), font=('Helvetica', 20), text_color='red', justification='center') text_element = SG.Text('',size=(10,2), font=('Helvetica', 20), text_color='red', justification='center')
form_rows = [[SG.Text('Non blocking GUI with updates', justification='center')], layout = [[SG.Text('Non blocking GUI with updates', justification='center')],
[output_element], [text_element],
[SG.T(' '*15), SG.Quit()]] [SG.T(' '*15), SG.Quit()]]
form.AddRows(form_rows) form.LayoutAndRead(layout, non_blocking=True)
form.Show(non_blocking=True)
for i in range(1,500): for i in range(1,500):
output_element.Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i/100), 60), i%100)) text_element.Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i/100), 60), i%100))
rc = form.Refresh() button, values = form.ReadNonBlocking()
if rc is None: # if user closed the window using X if values is None or button == 'Quit': # if user closed the window using X
break
button, values = rc
if button == 'Quit':
break break
time.sleep(.01) time.sleep(.01)
else: else:
# if the loop finished then need to close the form for the user # if the loop finished then need to close the form for the user
form.CloseNonBlockingForm() form.CloseNonBlockingForm()
# Use this context-manager-free version if your read of the form occurs far away in your code
# from the form creation (call to LayoutAndRead)
def NonBlockingPeriodicUpdateForm(): def NonBlockingPeriodicUpdateForm():
# Show a form that's a running counter # Show a form that's a running counter
form = SG.FlexForm('Running Timer', auto_size_text=True) form = SG.FlexForm('Running Timer', auto_size_text=True)
output_element = SG.Text('',size=(8,2), font=('Helvetica', 20)) text_element = SG.Text('',size=(10,2), font=('Helvetica', 20), justification='center')
form_rows = [[SG.Text('Non blocking GUI with updates')], form_rows = [[SG.Text('Non blocking GUI with updates')],
[output_element], [text_element],
[SG.Quit()]] [SG.T(' ' * 15), SG.Quit()]]
form.AddRows(form_rows) form.LayoutAndRead(form_rows, non_blocking=True)
form.Show(non_blocking=True)
for i in range(1,50000): for i in range(1,50000):
output_element.Update(f'{(i/100)/60:02d}:{(i/100)%60:02d}.{i%100:02d}') text_element.Update(f'{(i//100)//60:02d}:{(i//100)%60:02d}.{i%100:02d}')
rc = form.Refresh() button, values = form.ReadNonBlocking()
if rc is None or rc[0] == 'Quit': # if user closed the window using X or clicked Quit button if values is None or button == 'Quit': # if user closed the window using X or clicked Quit button
break break
time.sleep(.01) time.sleep(.01)
else: else:
# if the loop finished then need to close the form for the user # if the loop finished then need to close the form for the user
form.CloseNonBlockingForm() form.CloseNonBlockingForm()
del(form)
def DebugTest(): def DebugTest():
# SG.Print('How about we print a bunch of random numbers?', , size=(90,40)) # SG.Print('How about we print a bunch of random numbers?', , size=(90,40))
for i in range (1,300): for i in range (1,300):
SG.Print(i, randint(1, 1000), end='', sep='-') SG.Print(i, randint(1, 1000), end='', sep='-')
# SG.PrintClose()
def main(): def main():
SG.SetOptions(border_width=1, element_padding=(4,6), font=("Helvetica", 10), button_color=('white', SG.BLUES[0]), # SG.SetOptions(border_width=1, font=("Helvetica", 10), button_color=('white', SG.BLUES[0]), slider_border_width=1)
progress_meter_border_depth=0) Everything_NoContextManager()
SourceDestFolders()
Everything() Everything()
NonBlockingPeriodicUpdateForm_ContextManager() NonBlockingPeriodicUpdateForm_ContextManager()
NonBlockingPeriodicUpdateForm()
ChatBot()
Everything()
ProgressMeter() ProgressMeter()
SourceDestFolders()
ChatBot() ChatBot()
DebugTest() DebugTest()
SG.MsgBox('Done with all recipes')
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -17,7 +17,7 @@ DEFAULT_ELEMENT_PADDING = (5,3) # Padding between elements (row, col) in
DEFAULT_AUTOSIZE_TEXT = False DEFAULT_AUTOSIZE_TEXT = False
DEFAULT_FONT = ("Helvetica", 10) DEFAULT_FONT = ("Helvetica", 10)
DEFAULT_TEXT_JUSTIFICATION = 'left' DEFAULT_TEXT_JUSTIFICATION = 'left'
DEFAULT_BORDER_WIDTH = 4 DEFAULT_BORDER_WIDTH = 1
DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form
DEFAULT_DEBUG_WINDOW_SIZE = (80,20) DEFAULT_DEBUG_WINDOW_SIZE = (80,20)
MAX_SCROLLED_TEXT_BOX_HEIGHT = 50 MAX_SCROLLED_TEXT_BOX_HEIGHT = 50
@ -32,7 +32,8 @@ NICE_BUTTON_COLORS = ((GREENS[3], TANS[0]), ('#000000','#FFFFFF'),('#FFFFFF', '#
# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember # DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember
# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default # DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default
# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default # DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default
DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default
# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default
# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default # DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default
DEFAULT_ERROR_BUTTON_COLOR =("#FFFFFF", "#FF0000") DEFAULT_ERROR_BUTTON_COLOR =("#FFFFFF", "#FF0000")
DEFAULT_CANCEL_BUTTON_COLOR = (GREENS[3], TANS[0]) DEFAULT_CANCEL_BUTTON_COLOR = (GREENS[3], TANS[0])
@ -43,21 +44,35 @@ DEFAULT_PROGRESS_BAR_COLOR = (GREENS[3], GREENS[3]) # a nice green progress
# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar # DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar
# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar # DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar
DEFAULT_PROGRESS_BAR_SIZE = (35,25) # Size of Progress Bar (characters for length, pixels for width) DEFAULT_PROGRESS_BAR_SIZE = (35,25) # Size of Progress Bar (characters for length, pixels for width)
DEFAULT_PROGRESS_BAR_BORDER_WIDTH=2 DEFAULT_PROGRESS_BAR_BORDER_WIDTH=1
DEFAULT_PROGRESS_BAR_RELIEF = tk.SUNKEN DEFAULT_PROGRESS_BAR_RELIEF = tk.SUNKEN
DEFAULT_PROGRESS_BAR_STYLE = 'default' DEFAULT_PROGRESS_BAR_STYLE = 'default'
DEFAULT_METER_ORIENTATION = 'Horizontal' DEFAULT_METER_ORIENTATION = 'Horizontal'
DEFAULT_SLIDER_ORIENTATION = 'vertical'
DEFAULT_SLIDER_BORDER_WIDTH=1
DEFAULT_SLIDER_RELIEF = tk.SUNKEN
DEFAULT_LISTBOX_SELECT_MODE = tk.SINGLE
SELECT_MODE_MULTIPLE = tk.MULTIPLE
LISTBOX_SELECT_MODE_MULTIPLE = 'multiple'
SELECT_MODE_BROWSE = tk.BROWSE
LISTBOX_SELECT_MODE_BROWSE = 'browse'
SELECT_MODE_EXTENDED = tk.EXTENDED
LISTBOX_SELECT_MODE_EXTENDED = 'extended'
SELECT_MODE_SINGLE = tk.SINGLE
LISTBOX_SELECT_MODE_SINGLE = 'single'
# DEFAULT_METER_ORIENTATION = 'Vertical' # DEFAULT_METER_ORIENTATION = 'Vertical'
# ----====----====----==== Constants the user should NOT f-with ====----====----====----# # ----====----====----==== Constants the user should NOT f-with ====----====----====----#
ThisRow = 555666777 # magic number ThisRow = 555666777 # magic number
# Progress Bar Relief Choices # Progress Bar Relief Choices
# -relief # -relief
RAISED='raised' RELIEF_RAISED= 'raised'
SUNKEN='sunken' RELIEF_SUNKEN= 'sunken'
FLAT='flat' RELIEF_FLAT= 'flat'
RIDGE='ridge' RELIEF_RIDGE= 'ridge'
GROOVE='groove' RELIEF_GROOVE= 'groove'
SOLID = 'solid' RELIEF_SOLID = 'solid'
PROGRESS_BAR_STYLES = ('default','winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative') PROGRESS_BAR_STYLES = ('default','winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative')
# DEFAULT_WINDOW_ICON = '' # DEFAULT_WINDOW_ICON = ''
@ -99,6 +114,8 @@ ELEM_TYPE_INPUT_CHECKBOX = 8
ELEM_TYPE_INPUT_SPIN = 9 ELEM_TYPE_INPUT_SPIN = 9
ELEM_TYPE_BUTTON = 3 ELEM_TYPE_BUTTON = 3
ELEM_TYPE_IMAGE = 30 ELEM_TYPE_IMAGE = 30
ELEM_TYPE_INPUT_SLIDER = 10
ELEM_TYPE_INPUT_LISTBOX = 11
ELEM_TYPE_OUTPUT = 300 ELEM_TYPE_OUTPUT = 300
ELEM_TYPE_PROGRESS_BAR = 200 ELEM_TYPE_PROGRESS_BAR = 200
ELEM_TYPE_BLANK = 100 ELEM_TYPE_BLANK = 100
@ -200,6 +217,37 @@ class InputCombo(Element):
pass pass
super().__del__() super().__del__()
# ---------------------------------------------------------------------- #
# Combo #
# ---------------------------------------------------------------------- #
class Listbox(Element):
def __init__(self, values, select_mode=None, scale=(None, None), size=(None, None), auto_size_text=None, font=None):
self.Values = values
self.TKListBox = None
if select_mode == LISTBOX_SELECT_MODE_BROWSE:
self.SelectMode = SELECT_MODE_BROWSE
elif select_mode == LISTBOX_SELECT_MODE_EXTENDED:
self.SelectMode = SELECT_MODE_EXTENDED
elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE:
self.SelectMode = SELECT_MODE_MULTIPLE
elif select_mode == LISTBOX_SELECT_MODE_SINGLE:
self.SelectMode = SELECT_MODE_SINGLE
else:
self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE
super().__init__(ELEM_TYPE_INPUT_LISTBOX, scale=scale, size=size, auto_size_text=auto_size_text, font=font)
return
def __del__(self):
try:
self.TKListBox.__del__()
except:
pass
super().__del__()
# ---------------------------------------------------------------------- # # ---------------------------------------------------------------------- #
# Radio # # Radio #
# ---------------------------------------------------------------------- # # ---------------------------------------------------------------------- #
@ -361,11 +409,6 @@ class TKProgressBar():
# New Type of Widget that's a Text Widget in disguise # # New Type of Widget that's a Text Widget in disguise #
# ---------------------------------------------------------------------- # # ---------------------------------------------------------------------- #
class TKOutput(tk.Frame): class TKOutput(tk.Frame):
''' Demonstrate python interpreter output in Tkinter Text widget
type python expression in the entry, hit DoIt and see the results
in the text pane.'''
# previous_stderr = None
# previous_stdout = None
def __init__(self, parent, width, height, bd): def __init__(self, parent, width, height, bd):
tk.Frame.__init__(self, parent) tk.Frame.__init__(self, parent)
self.output = tk.Text(parent, width=width, height=height, bd=bd) self.output = tk.Text(parent, width=width, height=height, bd=bd)
@ -552,6 +595,23 @@ class Image(Element):
def __del__(self): def __del__(self):
super().__del__() super().__del__()
# ---------------------------------------------------------------------- #
# Slider #
# ---------------------------------------------------------------------- #
class Slider(Element):
def __init__(self, range=(None,None), default_value=None, orientation=None, border_width=None, relief=None, scale=(None, None), size=(None, None), font=None):
self.TKScale = None
self.Range = (1,10) if range == (None, None) else range
self.DefaultValue = 5 if default_value is None else default_value
self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION
self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH
self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF
super().__init__(ELEM_TYPE_INPUT_SLIDER, scale=scale, size=size, font=font)
return
def __del__(self):
super().__del__()
# ------------------------------------------------------------------------- # # ------------------------------------------------------------------------- #
@ -629,9 +689,17 @@ class FlexForm:
for row in rows: for row in rows:
self.AddRow(*row) self.AddRow(*row)
def LayoutAndShow(self,rows): def Layout(self,rows):
self.AddRows(rows) self.AddRows(rows)
self.Show()
def LayoutAndShow(self,rows, non_blocking=False):
self.AddRows(rows)
self.Show(non_blocking=non_blocking)
return self.ReturnValues
def LayoutAndRead(self,rows, non_blocking=False):
self.AddRows(rows)
self.Show(non_blocking=non_blocking)
return self.ReturnValues return self.ReturnValues
# ------------------------- ShowForm THIS IS IT! ------------------------- # # ------------------------- ShowForm THIS IS IT! ------------------------- #
@ -676,27 +744,41 @@ class FlexForm:
pass pass
def Read(self): def Read(self):
if self.TKrootDestroyed: return None if self.TKrootDestroyed:
if not self.TKrootDestroyed and not self.Shown: return None, None
if not self.Shown:
self.Show() self.Show()
elif not self.TKrootDestroyed: else:
self.TKroot.mainloop() self.TKroot.mainloop()
if self.RootNeedsDestroying: if self.RootNeedsDestroying:
self.TKroot.destroy() self.TKroot.destroy()
_my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0 _my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0
return(BuildResults(self)) return BuildResults(self)
def Refresh(self, Message=''): def ReadNonBlocking(self, Message=''):
if self.TKrootDestroyed: if self.TKrootDestroyed:
return None return None, None
if Message: if Message:
print(Message) print(Message)
try: try:
self.TKroot.update() rc = self.TKroot.update()
except: except:
self.TKrootDestroyed = True self.TKrootDestroyed = True
_my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0 _my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0
return(BuildResults(self)) return BuildResults(self)
# LEGACY version of ReadNonBlocking
def Refresh(self, Message=''):
if self.TKrootDestroyed:
return None, None
if Message:
print(Message)
try:
rc = self.TKroot.update()
except:
self.TKrootDestroyed = True
_my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0
return BuildResults(self)
def Close(self): def Close(self):
try: try:
@ -874,8 +956,14 @@ def InitializeResults(form):
elif element.Type == ELEM_TYPE_INPUT_COMBO: elif element.Type == ELEM_TYPE_INPUT_COMBO:
r.append(element.TextInputDefault) r.append(element.TextInputDefault)
return_vals.append(None) return_vals.append(None)
elif element.Type == ELEM_TYPE_INPUT_LISTBOX:
r.append(None)
return_vals.append(None)
elif element.Type == ELEM_TYPE_INPUT_SPIN: elif element.Type == ELEM_TYPE_INPUT_SPIN:
r.append(element.TextInputDefault) r.append(element.DefaultValue)
return_vals.append(None)
elif element.Type == ELEM_TYPE_INPUT_SLIDER:
r.append(element.DefaultValue)
return_vals.append(None) return_vals.append(None)
results.append(r) results.append(r)
form.Results=results form.Results=results
@ -930,6 +1018,11 @@ def BuildResults(form):
value=element.TKStringVar.get() value=element.TKStringVar.get()
results[row_num][col_num] = value results[row_num][col_num] = value
input_values.append(value) input_values.append(value)
elif element.Type == ELEM_TYPE_INPUT_LISTBOX:
items=element.TKListbox.curselection()
value = [element.Values[int(item)] for item in items]
results[row_num][col_num] = value
input_values.append(value)
elif element.Type == ELEM_TYPE_INPUT_SPIN: elif element.Type == ELEM_TYPE_INPUT_SPIN:
try: try:
value=element.TKStringVar.get() value=element.TKStringVar.get()
@ -937,6 +1030,13 @@ def BuildResults(form):
value = 0 value = 0
results[row_num][col_num] = value results[row_num][col_num] = value
input_values.append(value) input_values.append(value)
elif element.Type == ELEM_TYPE_INPUT_SLIDER:
try:
value=element.TKIntVar.get()
except:
value = 0
results[row_num][col_num] = value
input_values.append(value)
elif element.Type == ELEM_TYPE_INPUT_MULTILINE: elif element.Type == ELEM_TYPE_INPUT_MULTILINE:
try: try:
value=element.TKText.get(1.0, tk.END) value=element.TKText.get(1.0, tk.END)
@ -947,7 +1047,7 @@ def BuildResults(form):
results[row_num][col_num] = value results[row_num][col_num] = value
input_values.append(value) input_values.append(value)
return_value = (button_pressed_text,input_values) return_value = button_pressed_text,input_values
form.ReturnValues = return_value form.ReturnValues = return_value
form.ResultsBuilt = True form.ResultsBuilt = True
return return_value return return_value
@ -957,6 +1057,8 @@ def BuildResults(form):
# ===================================== TK CODE STARTS HERE ====================================================== # # ===================================== TK CODE STARTS HERE ====================================================== #
# ------------------------------------------------------------------------------------------------------------------ # # ------------------------------------------------------------------------------------------------------------------ #
def ConvertFlexToTK(MyFlexForm): def ConvertFlexToTK(MyFlexForm):
def CharWidthInPixels():
return tkinter.font.Font().measure('A') # single character width
master = MyFlexForm.TKroot master = MyFlexForm.TKroot
# only set title on non-tabbed forms # only set title on non-tabbed forms
if not MyFlexForm.IsTabbedForm: if not MyFlexForm.IsTabbedForm:
@ -1081,6 +1183,18 @@ def ConvertFlexToTK(MyFlexForm):
element.TKCombo['values'] = element.Values element.TKCombo['values'] = element.Values
element.TKCombo.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1]) element.TKCombo.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1])
element.TKCombo.current(0) element.TKCombo.current(0)
# ------------------------- LISTBOX (Drop Down) element ------------------------- #
elif element_type == ELEM_TYPE_INPUT_LISTBOX:
max_line_len = max([len(str(l)) for l in element.Values])
if auto_size_text is False: width=element_size[0]
else: width = max_line_len
element.TKStringVar = tk.StringVar()
element.TKListbox= tk.Listbox(tk_row_frame, height=element_size[1], width=width, selectmode=element.SelectMode, font=font)
for item in element.Values:
element.TKListbox.insert(tk.END, item)
element.TKListbox.selection_set(0,0)
element.TKListbox.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1])
# ------------------------- INPUT MULTI LINE element ------------------------- # # ------------------------- INPUT MULTI LINE element ------------------------- #
elif element_type == ELEM_TYPE_INPUT_MULTILINE: elif element_type == ELEM_TYPE_INPUT_MULTILINE:
default_text = element.DefaultText default_text = element.DefaultText
@ -1162,6 +1276,21 @@ def ConvertFlexToTK(MyFlexForm):
tktext_label.image = photo tktext_label.image = photo
# tktext_label.configure(anchor=tk.NW, image=photo) # tktext_label.configure(anchor=tk.NW, image=photo)
tktext_label.pack(side=tk.LEFT) tktext_label.pack(side=tk.LEFT)
# ------------------------- SLIDER Box element ------------------------- #
elif element_type == ELEM_TYPE_INPUT_SLIDER:
slider_length = element_size[0] * CharWidthInPixels()
slider_width = element_size[1]
element.TKIntVar = tk.IntVar()
element.TKIntVar.set(element.DefaultValue)
if element.Orientation[0] == 'v':
range_from = element.Range[1]
range_to = element.Range[0]
else:
range_from = element.Range[0]
range_to = element.Range[1]
tkscale = tk.Scale(tk_row_frame, orient=element.Orientation, variable=element.TKIntVar, from_=range_from, to_=range_to, length=slider_length, width=slider_width , bd=element.BorderWidth, relief=element.Relief, font=font)
# tktext_label.configure(anchor=tk.NW, image=photo)
tkscale.pack(side=tk.LEFT)
#............................DONE WITH ROW pack the row of widgets ..........................# #............................DONE WITH ROW pack the row of widgets ..........................#
# done with row, pack the row of widgets # done with row, pack the row of widgets
tk_row_frame.grid(row=row_num+2, sticky=tk.W, padx=DEFAULT_MARGINS[0]) tk_row_frame.grid(row=row_num+2, sticky=tk.W, padx=DEFAULT_MARGINS[0])
@ -1520,7 +1649,6 @@ def ProgressMeterUpdate(bar, value, *args):
if bar.BarExpired: return False if bar.BarExpired: return False
message, w, h = ConvertArgsToSingleString(*args) message, w, h = ConvertArgsToSingleString(*args)
bar.TextToDisplay = message bar.TextToDisplay = message
bar.CurrentValue = value bar.CurrentValue = value
rc = bar.UpdateBar(value) rc = bar.UpdateBar(value)
@ -1529,8 +1657,8 @@ def ProgressMeterUpdate(bar, value, *args):
bar.ParentForm.Close() bar.ParentForm.Close()
if bar.ParentForm.RootNeedsDestroying: if bar.ParentForm.RootNeedsDestroying:
try: try:
bar.ParentForm.TKroot.destroy()
_my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0 _my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0
bar.ParentForm.TKroot.destroy()
except: pass except: pass
bar.ParentForm.RootNeedsDestroying = False bar.ParentForm.RootNeedsDestroying = False
bar.ParentForm.__del__() bar.ParentForm.__del__()
@ -1841,9 +1969,12 @@ def SetGlobalIcon(icon):
# ============================== SetOptions =========# # ============================== SetOptions =========#
# Sets the icon to be used by default # # Sets the icon to be used by default #
# ===================================================# # ===================================================#
def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), margins=(None,None), element_padding=(None,None), def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), margins=(None,None),
auto_size_text=None, font=None, border_width=None, autoclose_time=None, message_box_line_width=None, element_padding=(None,None),auto_size_text=None, font=None, border_width=None,
slider_border_width=None, slider_relief=None, slider_orientation=None,
autoclose_time=None, message_box_line_width=None,
progress_meter_border_depth=None, text_justification=None, debug_win_size=(None,None)): progress_meter_border_depth=None, text_justification=None, debug_win_size=(None,None)):
global DEFAULT_ELEMENT_SIZE global DEFAULT_ELEMENT_SIZE
global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term
global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels
@ -1856,6 +1987,9 @@ def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), ma
global DEFAULT_PROGRESS_BAR_BORDER_WIDTH global DEFAULT_PROGRESS_BAR_BORDER_WIDTH
global DEFAULT_TEXT_JUSTIFICATION global DEFAULT_TEXT_JUSTIFICATION
global DEFAULT_DEBUG_WINDOW_SIZE global DEFAULT_DEBUG_WINDOW_SIZE
global DEFAULT_SLIDER_BORDER_WIDTH
global DEFAULT_SLIDER_RELIEF
global DEFAULT_SLIDER_ORIENTATION
global _my_windows global _my_windows
if icon: if icon:
@ -1896,6 +2030,15 @@ def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), ma
if progress_meter_border_depth != None: if progress_meter_border_depth != None:
DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth
if slider_border_width != None:
DEFAULT_SLIDER_BORDER_WIDTH = slider_border_width
if slider_orientation != None:
DEFAULT_SLIDER_ORIENTATION = slider_orientation
if slider_relief != None:
DEFAULT_SLIDER_RELIEF = slider_relief
if text_justification != None: if text_justification != None:
DEFAULT_TEXT_JUSTIFICATION = text_justification DEFAULT_TEXT_JUSTIFICATION = text_justification
@ -1904,12 +2047,21 @@ def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), ma
return True return True
# ============================== sprint ======#fddddddddddddddddddddddd
# ============================== sprint ======#
# Is identical to the Scrolled Text Box # # Is identical to the Scrolled Text Box #
# Provides a crude 'print' mechanism but in a # # Provides a crude 'print' mechanism but in a #
# GUI environment # # GUI environment #
# ============================================# # ============================================#
sprint=ScrolledTextBox sprint=ScrolledTextBox
# Converts an object's contents into a nice printable string. Great for dumping debug data
def ObjToString_old(obj):
return str(obj.__class__) + '\n' + '\n'.join(
(repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__)))
def ObjToString(obj, extra=' '):
return str(obj.__class__) + '\n' + '\n'.join(
(extra + (str(item) + ' = ' +
(ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str(
obj.__dict__[item])))
for item in sorted(obj.__dict__)))

596
readme.md

File diff suppressed because it is too large Load Diff