LOTS of changes and new additions
Text justification for Text Elems NEW Image Element OutputFlush renamed to Refresh More shorthand functions - Combo, Dropdown, Drop, EasyPrint - output of stdout, stderr to a window
This commit is contained in:
parent
fb2fe90d37
commit
f331661a3a
|
@ -1,19 +1,21 @@
|
|||
import time
|
||||
from random import randint
|
||||
import random
|
||||
import string
|
||||
import PySimpleGUI as SG
|
||||
|
||||
|
||||
def SourceDestFolders():
|
||||
with SG.FlexForm('Demo Source / Destination Folders', auto_size_text=True) as form:
|
||||
form_rows = [[SG.Text('Enter the Source and Destination folders')],
|
||||
[SG.Text('Choose Source and Destination Folders')],
|
||||
[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.Text('Source Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Source')],
|
||||
[SG.Text('Destination Folder', size=(15, 1), auto_size_text=False, justification='right'), SG.InputText('Dest'), SG.FolderBrowse()],
|
||||
[SG.Submit(), SG.Cancel()]]
|
||||
|
||||
(button, (source, dest)) = form.LayoutAndShow(form_rows)
|
||||
button, (source, dest) = form.LayoutAndShow(form_rows)
|
||||
if button == 'Submit':
|
||||
# do something useful with the inputs
|
||||
SG.MsgBox('Submitted', 'The user entered source:', source, 'Destination folder:', dest)
|
||||
SG.MsgBox('Submitted', 'The user entered source:', source, 'Destination folder:', dest, 'Using button', button)
|
||||
else:
|
||||
SG.MsgBoxError('Cancelled', 'User Cancelled')
|
||||
|
||||
|
@ -33,9 +35,9 @@ def Everything_NoContextManager():
|
|||
[SG.SimpleButton('Your very own button', button_color=('white', 'green'))],
|
||||
[SG.Submit(), SG.Cancel()]]
|
||||
|
||||
(button, (values)) = form.LayoutAndShow(layout)
|
||||
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)
|
||||
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():
|
||||
|
@ -45,16 +47,18 @@ def Everything():
|
|||
[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('Your very own button', button_color=('white', 'green'))],
|
||||
[SG.SimpleButton('Custom Button', button_color=('white', 'green'))],
|
||||
[SG.Submit(), SG.Cancel()]]
|
||||
|
||||
(button, (values)) = form.LayoutAndShow(layout)
|
||||
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)
|
||||
|
||||
|
@ -62,6 +66,26 @@ def ProgressMeter():
|
|||
for i in range(1,10000):
|
||||
if not SG.EasyProgressMeter('My Meter', i+1, 10000): break
|
||||
|
||||
def RunningTimer():
|
||||
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
|
||||
|
@ -70,7 +94,7 @@ def ChatBot():
|
|||
form.AddRow(SG.Text('This is where standard out is being routed', size=[40, 1]))
|
||||
form.AddRow(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])))
|
||||
(button, value) = form.Read()
|
||||
button, value = form.Read()
|
||||
|
||||
# ---===--- Loop taking in user input and using it to query HowDoI web oracle --- #
|
||||
while True:
|
||||
|
@ -78,22 +102,22 @@ def ChatBot():
|
|||
print(value)
|
||||
else:
|
||||
break
|
||||
(button, value) = form.Read()
|
||||
button, value = form.Read()
|
||||
|
||||
|
||||
def NonBlockingPeriodicUpdateForm_ContextManager():
|
||||
# Show a form that's a running counter
|
||||
with SG.FlexForm('Running Timer', auto_size_text=True) as form:
|
||||
output_element = SG.Text('',size=(8,2), font=('Helvetica', 20), text_color='red')
|
||||
form_rows = [[SG.Text('None blocking GUI with updates')],
|
||||
output_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')],
|
||||
[output_element],
|
||||
[SG.Quit()]]
|
||||
[SG.T(' '*15), SG.Quit()]]
|
||||
form.AddRows(form_rows)
|
||||
form.Show(non_blocking=True)
|
||||
|
||||
for i in range(1,500):
|
||||
output_element.Update('{:02d}:{:02d}.{:02d}'.format(*divmod(int(i/100), 60), i%100))
|
||||
rc = form.OutputFlush()
|
||||
rc = form.Refresh()
|
||||
if rc is None: # if user closed the window using X
|
||||
break
|
||||
button, values = rc
|
||||
|
@ -117,11 +141,8 @@ def NonBlockingPeriodicUpdateForm():
|
|||
|
||||
for i in range(1,50000):
|
||||
output_element.Update(f'{(i/100)/60:02d}:{(i/100)%60:02d}.{i%100:02d}')
|
||||
rc = form.OutputFlush()
|
||||
if rc is None: # if user closed the window using X
|
||||
break
|
||||
button, values = rc
|
||||
if button == 'Quit':
|
||||
rc = form.Refresh()
|
||||
if rc is None or rc[0] == 'Quit': # if user closed the window using X or clicked Quit button
|
||||
break
|
||||
time.sleep(.01)
|
||||
else:
|
||||
|
@ -129,40 +150,24 @@ def NonBlockingPeriodicUpdateForm():
|
|||
form.CloseNonBlockingForm()
|
||||
|
||||
|
||||
def NonBlockingScrolledPrintForm():
|
||||
# Show a form that's a running counter
|
||||
form = SG.FlexForm('Scrolled Print', auto_size_text=True, font=('Courier New', 12))
|
||||
output_element = SG.Output(size=(42,10))
|
||||
form_rows = [[SG.Text('Scrolled print output')],
|
||||
[output_element],
|
||||
[SG.Quit()]]
|
||||
form.AddRows(form_rows)
|
||||
form.Show(non_blocking=True) # Show a ;non-blocking form, returns immediately
|
||||
|
||||
for i in range(1,50000):
|
||||
print(f'{i} ', end="") # all print output will go to the scrolled text box
|
||||
# must call OutputFlush on a periodic basis to keep GUI alive
|
||||
rc = form.OutputFlush()
|
||||
if rc is None: # if user closed the window using X
|
||||
break
|
||||
button, values = rc
|
||||
if button == 'Quit': # if user cliced Quit button
|
||||
break
|
||||
else: # if the loop finished then need to close the form for the user
|
||||
form.CloseNonBlockingForm()
|
||||
def DebugTest():
|
||||
# SG.Print('How about we print a bunch of random numbers?', , size=(90,40))
|
||||
for i in range (1,300):
|
||||
SG.Print(i, randint(1, 1000), end='', sep='-')
|
||||
|
||||
# SG.PrintClose()
|
||||
|
||||
|
||||
def main():
|
||||
SG.SetOptions(border_width=4, element_padding=(4,6), font=("Helvetica", 10), button_color=('white', SG.BLUES[0]),
|
||||
progress_meter_border_depth=4)
|
||||
SG.SetOptions(border_width=1, element_padding=(4,6), font=("Helvetica", 10), button_color=('white', SG.BLUES[0]),
|
||||
progress_meter_border_depth=0)
|
||||
SourceDestFolders()
|
||||
Everything()
|
||||
NonBlockingPeriodicUpdateForm_ContextManager()
|
||||
ProgressMeter()
|
||||
ChatBot()
|
||||
NonBlockingScrolledPrintForm()
|
||||
NonBlockingPeriodicUpdateForm_ContextManager()
|
||||
Everything_NoContextManager()
|
||||
Everything()
|
||||
DebugTest()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
exit(69)
|
||||
|
|
263
PySimpleGUI.py
263
PySimpleGUI.py
|
@ -16,9 +16,10 @@ DEFAULT_MARGINS = (10,5) # Margins for each LEFT/RIGHT margin is
|
|||
DEFAULT_ELEMENT_PADDING = (5,3) # Padding between elements (row, col) in pixels
|
||||
DEFAULT_AUTOSIZE_TEXT = False
|
||||
DEFAULT_FONT = ("Helvetica", 10)
|
||||
|
||||
DEFAULT_TEXT_JUSTIFICATION = 'left'
|
||||
DEFAULT_BORDER_WIDTH = 4
|
||||
DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form
|
||||
DEFAULT_DEBUG_WINDOW_SIZE = (80,20)
|
||||
MAX_SCROLLED_TEXT_BOX_HEIGHT = 50
|
||||
#################### COLOR STUFF ####################
|
||||
BLUES = ("#082567","#0A37A3","#00345B")
|
||||
|
@ -89,17 +90,18 @@ READ_FORM = 7
|
|||
|
||||
# ------------------------- Element types ------------------------- #
|
||||
# class ElementType(Enum):
|
||||
TEXT = 1
|
||||
INPUT_TEXT = 20
|
||||
INPUT_COMBO = 21
|
||||
INPUT_RADIO = 5
|
||||
INPUT_MULTILINE = 7
|
||||
INPUT_CHECKBOX = 8
|
||||
INPUT_SPIN = 9
|
||||
BUTTON = 3
|
||||
OUTPUT = 300
|
||||
PROGRESS_BAR = 200
|
||||
BLANK = 100
|
||||
ELEM_TYPE_TEXT = 1
|
||||
ELEM_TYPE_INPUT_TEXT = 20
|
||||
ELEM_TYPE_INPUT_COMBO = 21
|
||||
ELEM_TYPE_INPUT_RADIO = 5
|
||||
ELEM_TYPE_INPUT_MULTILINE = 7
|
||||
ELEM_TYPE_INPUT_CHECKBOX = 8
|
||||
ELEM_TYPE_INPUT_SPIN = 9
|
||||
ELEM_TYPE_BUTTON = 3
|
||||
ELEM_TYPE_IMAGE = 30
|
||||
ELEM_TYPE_OUTPUT = 300
|
||||
ELEM_TYPE_PROGRESS_BAR = 200
|
||||
ELEM_TYPE_BLANK = 100
|
||||
|
||||
# ------------------------- MsgBox Buttons Types ------------------------- #
|
||||
MSG_BOX_YES_NO = 1
|
||||
|
@ -131,6 +133,7 @@ class Element():
|
|||
self.TKIntVar = None
|
||||
self.TKText = None
|
||||
self.TKEntry = None
|
||||
self.TKImage = None
|
||||
|
||||
self.ParentForm=None
|
||||
self.TextInputDefault = None
|
||||
|
@ -163,7 +166,7 @@ class InputText(Element):
|
|||
def __init__(self, default_text ='', scale=(None, None), size=(None, None), auto_size_text=None, password_char=''):
|
||||
self.DefaultText = default_text
|
||||
self.PasswordCharacter = password_char
|
||||
super().__init__(INPUT_TEXT, scale, size, auto_size_text)
|
||||
super().__init__(ELEM_TYPE_INPUT_TEXT, scale, size, auto_size_text)
|
||||
return
|
||||
|
||||
def ReturnKeyHandler(self, event):
|
||||
|
@ -171,7 +174,7 @@ class InputText(Element):
|
|||
# search through this form and find the first button that will exit the form
|
||||
for row in MyForm.Rows:
|
||||
for element in row.Elements:
|
||||
if element.Type == BUTTON:
|
||||
if element.Type == ELEM_TYPE_BUTTON:
|
||||
if element.BType == CLOSES_WIN or element.BType == READ_FORM:
|
||||
element.ButtonCallBack()
|
||||
return
|
||||
|
@ -187,7 +190,7 @@ class InputCombo(Element):
|
|||
def __init__(self, values, scale=(None, None), size=(None, None), auto_size_text=None):
|
||||
self.Values = values
|
||||
self.TKComboBox = None
|
||||
super().__init__(INPUT_COMBO, scale, size, auto_size_text)
|
||||
super().__init__(ELEM_TYPE_INPUT_COMBO, scale, size, auto_size_text)
|
||||
return
|
||||
|
||||
def __del__(self):
|
||||
|
@ -207,7 +210,7 @@ class Radio(Element):
|
|||
self.TKRadio = None
|
||||
self.GroupID = group_id
|
||||
self.Value = None
|
||||
super().__init__(INPUT_RADIO, scale, size, auto_size_text, font)
|
||||
super().__init__(ELEM_TYPE_INPUT_RADIO, scale, size, auto_size_text, font)
|
||||
return
|
||||
|
||||
def __del__(self):
|
||||
|
@ -227,7 +230,7 @@ class Checkbox(Element):
|
|||
self.Value = None
|
||||
self.TKCheckbox = None
|
||||
|
||||
super().__init__(INPUT_CHECKBOX, scale, size, auto_size_text, font)
|
||||
super().__init__(ELEM_TYPE_INPUT_CHECKBOX, scale, size, auto_size_text, font)
|
||||
return
|
||||
|
||||
def __del__(self):
|
||||
|
@ -248,7 +251,7 @@ class Spin(Element):
|
|||
self.Values = values
|
||||
self.DefaultValue = initial_value
|
||||
self.TKSpinBox = None
|
||||
super().__init__(INPUT_SPIN, scale, size, auto_size_text, font=font)
|
||||
super().__init__(ELEM_TYPE_INPUT_SPIN, scale, size, auto_size_text, font=font)
|
||||
return
|
||||
|
||||
def __del__(self):
|
||||
|
@ -265,7 +268,7 @@ class Multiline(Element):
|
|||
def __init__(self, default_text='', enter_submits = False, scale=(None, None), size=(None, None), auto_size_text=None):
|
||||
self.DefaultText = default_text
|
||||
self.EnterSubmits = enter_submits
|
||||
super().__init__(INPUT_MULTILINE, scale, size, auto_size_text)
|
||||
super().__init__(ELEM_TYPE_INPUT_MULTILINE, scale, size, auto_size_text)
|
||||
return
|
||||
|
||||
def ReturnKeyHandler(self, event):
|
||||
|
@ -273,7 +276,7 @@ class Multiline(Element):
|
|||
# search through this form and find the first button that will exit the form
|
||||
for row in MyForm.Rows:
|
||||
for element in row.Elements:
|
||||
if element.Type == BUTTON:
|
||||
if element.Type == ELEM_TYPE_BUTTON:
|
||||
if element.BType == CLOSES_WIN or element.BType == READ_FORM:
|
||||
element.ButtonCallBack()
|
||||
return
|
||||
|
@ -285,12 +288,13 @@ class Multiline(Element):
|
|||
# Text #
|
||||
# ---------------------------------------------------------------------- #
|
||||
class Text(Element):
|
||||
def __init__(self, text, scale=(None, None), size=(None, None), auto_size_text=None, font=None, text_color=None):
|
||||
def __init__(self, text, scale=(None, None), size=(None, None), auto_size_text=None, font=None, text_color=None, justification=None):
|
||||
self.DisplayText = text
|
||||
self.TextColor = text_color if text_color else 'black'
|
||||
self.Justification = justification if justification else DEFAULT_TEXT_JUSTIFICATION
|
||||
# self.Font = Font if Font else DEFAULT_FONT
|
||||
# i=1/0
|
||||
super().__init__(TEXT, scale, size, auto_size_text, font=font if font else DEFAULT_FONT)
|
||||
super().__init__(ELEM_TYPE_TEXT, scale, size, auto_size_text, font=font if font else DEFAULT_FONT)
|
||||
return
|
||||
|
||||
def Update(self, NewValue):
|
||||
|
@ -394,11 +398,12 @@ in the text pane.'''
|
|||
|
||||
def __del__(self):
|
||||
sys.stdout = self.previous_stdout
|
||||
sys.stderr = self.previous_stderr
|
||||
|
||||
class Output(Element):
|
||||
def __init__(self, scale=(None, None), size=(None, None)):
|
||||
self.TKOut = None
|
||||
super().__init__(OUTPUT, scale, size)
|
||||
super().__init__(ELEM_TYPE_OUTPUT, scale, size)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
|
@ -419,7 +424,7 @@ class Button(Element):
|
|||
self.ButtonText = button_text
|
||||
self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR
|
||||
self.UserData = None
|
||||
super().__init__(BUTTON, scale, size, auto_size_text, font=font)
|
||||
super().__init__(ELEM_TYPE_BUTTON, scale, size, auto_size_text, font=font)
|
||||
return
|
||||
|
||||
# ------- Button Callback ------- #
|
||||
|
@ -478,7 +483,7 @@ class Button(Element):
|
|||
# search through this form and find the first button that will exit the form
|
||||
for row in MyForm.Rows:
|
||||
for element in row.Elements:
|
||||
if element.Type == BUTTON:
|
||||
if element.Type == ELEM_TYPE_BUTTON:
|
||||
if element.BType == CLOSES_WIN or element.BType == READ_FORM:
|
||||
element.ButtonCallBack()
|
||||
return
|
||||
|
@ -506,7 +511,7 @@ class ProgressBar(Element):
|
|||
self.BorderWidth = border_width if border_width else DEFAULT_PROGRESS_BAR_BORDER_WIDTH
|
||||
self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF
|
||||
self.BarExpired = False
|
||||
super().__init__(PROGRESS_BAR, scale, size, auto_size_text)
|
||||
super().__init__(ELEM_TYPE_PROGRESS_BAR, scale, size, auto_size_text)
|
||||
return
|
||||
|
||||
def UpdateBar(self, current_count):
|
||||
|
@ -524,7 +529,7 @@ class ProgressBar(Element):
|
|||
try:
|
||||
self.ParentForm.TKroot.update()
|
||||
except:
|
||||
_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 False
|
||||
return True
|
||||
|
||||
|
@ -535,6 +540,20 @@ class ProgressBar(Element):
|
|||
pass
|
||||
super().__del__()
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Image #
|
||||
# ---------------------------------------------------------------------- #
|
||||
class Image(Element):
|
||||
def __init__(self, filename, scale=(None, None), size=(None, None), auto_size_text=None):
|
||||
self.Filename = filename
|
||||
super().__init__(ELEM_TYPE_IMAGE, scale=scale, size=size, auto_size_text=auto_size_text)
|
||||
return
|
||||
|
||||
def __del__(self):
|
||||
super().__del__()
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------- #
|
||||
# Row CLASS #
|
||||
# ------------------------------------------------------------------------- #
|
||||
|
@ -589,6 +608,7 @@ class FlexForm:
|
|||
self.RootNeedsDestroying = False
|
||||
self.Shown = False
|
||||
self.ReturnValues = None
|
||||
self.ResultsBuilt = False
|
||||
|
||||
# ------------------------- Add ONE Row to Form ------------------------- #
|
||||
def AddRow(self, *args, auto_size_text=None):
|
||||
|
@ -663,31 +683,37 @@ class FlexForm:
|
|||
self.TKroot.mainloop()
|
||||
if self.RootNeedsDestroying:
|
||||
self.TKroot.destroy()
|
||||
_my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0
|
||||
return(BuildResults(self))
|
||||
|
||||
def OutputFlush(self, Message=''):
|
||||
if self.TKrootDestroyed: return None
|
||||
def Refresh(self, Message=''):
|
||||
if self.TKrootDestroyed:
|
||||
return None
|
||||
if Message:
|
||||
print(Message)
|
||||
try:
|
||||
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):
|
||||
try:
|
||||
self.TKroot.update()
|
||||
except: pass
|
||||
results = BuildResults(self)
|
||||
if not self.NonBlocking:
|
||||
results = BuildResults(self)
|
||||
if self.TKrootDestroyed:
|
||||
return results
|
||||
return None
|
||||
self.TKrootDestroyed = True
|
||||
self.RootNeedsDestroying = True
|
||||
return results
|
||||
return None
|
||||
|
||||
def CloseNonBlockingForm(self):
|
||||
self.TKroot.destroy()
|
||||
try:
|
||||
self.TKroot.destroy()
|
||||
except: pass
|
||||
_my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0
|
||||
|
||||
def OnClosingCallback(self):
|
||||
|
@ -735,6 +761,7 @@ class UberForm():
|
|||
if not self.TKrootDestroyed:
|
||||
self.TKrootDestroyed = True
|
||||
self.TKroot.destroy()
|
||||
_my_windows.NumOpenWindows -= 1 * (_my_windows.NumOpenWindows != 0) # decrement if not 0
|
||||
|
||||
def __del__(self):
|
||||
return
|
||||
|
@ -750,12 +777,21 @@ def In(default_text ='', scale=(None, None), size=(None, None), auto_size_text=N
|
|||
def Input(default_text ='', scale=(None, None), size=(None, None), auto_size_text=None):
|
||||
return InputText(default_text=default_text, scale=scale, size=size, auto_size_text=auto_size_text)
|
||||
|
||||
# ------------------------- TEXT Element lazy functions ------------------------- #
|
||||
def Txt(display_text, scale=(None, None), size=(None, None), auto_size_text=None, font=None, text_color=None):
|
||||
return Text(display_text, scale=scale, size=size, auto_size_text=auto_size_text, font=font, text_color=text_color)
|
||||
# ------------------------- INPUT COMBO Element lazy functions ------------------------- #
|
||||
def Combo(values, scale=(None, None), size=(None, None), auto_size_text=None):
|
||||
return InputCombo(values=values, scale=scale, size=size, auto_size_text=auto_size_text)
|
||||
|
||||
def T(display_text, scale=(None, None), size=(None, None), auto_size_text=None, font=None, text_color=None):
|
||||
return Text(display_text, scale=scale, size=size, auto_size_text=auto_size_text, font=font, text_color=text_color)
|
||||
def DropDown(values, scale=(None, None), size=(None, None), auto_size_text=None):
|
||||
return InputCombo(values=values, scale=scale, size=size, auto_size_text=auto_size_text)
|
||||
|
||||
def Drop(values, scale=(None, None), size=(None, None), auto_size_text=None):
|
||||
return InputCombo(values=values, scale=scale, size=size, auto_size_text=auto_size_text)
|
||||
# ------------------------- TEXT Element lazy functions ------------------------- #
|
||||
def Txt(display_text, scale=(None, None), size=(None, None), auto_size_text=None, font=None, text_color=None, justification=None):
|
||||
return Text(display_text, scale=scale, size=size, auto_size_text=auto_size_text, font=font, text_color=text_color, justification=justification)
|
||||
|
||||
def T(display_text, scale=(None, None), size=(None, None), auto_size_text=None, font=None, text_color=None, justification=None):
|
||||
return Text(display_text, scale=scale, size=size, auto_size_text=auto_size_text, font=font, text_color=text_color, justification=justification)
|
||||
|
||||
# ------------------------- FOLDER BROWSE Element lazy function ------------------------- #
|
||||
def FolderBrowse(target=(ThisRow, -1), button_text='Browse', scale=(None, None), size=(None, None), auto_size_text=None, button_color=None):
|
||||
|
@ -815,28 +851,30 @@ def InitializeResults(form):
|
|||
for row_num,row in enumerate(form.Rows):
|
||||
r = []
|
||||
for element in row.Elements:
|
||||
if element.Type == TEXT:
|
||||
if element.Type == ELEM_TYPE_TEXT:
|
||||
r.append(None)
|
||||
elif element.Type == INPUT_TEXT:
|
||||
if element.Type == ELEM_TYPE_IMAGE:
|
||||
r.append(None)
|
||||
elif element.Type == ELEM_TYPE_INPUT_TEXT:
|
||||
r.append(element.TextInputDefault)
|
||||
return_vals.append(None)
|
||||
elif element.Type == INPUT_MULTILINE:
|
||||
elif element.Type == ELEM_TYPE_INPUT_MULTILINE:
|
||||
r.append(element.TextInputDefault)
|
||||
return_vals.append(None)
|
||||
elif element.Type == BUTTON:
|
||||
elif element.Type == ELEM_TYPE_BUTTON:
|
||||
r.append(False)
|
||||
elif element.Type == PROGRESS_BAR:
|
||||
elif element.Type == ELEM_TYPE_PROGRESS_BAR:
|
||||
r.append(None)
|
||||
elif element.Type == INPUT_CHECKBOX:
|
||||
elif element.Type == ELEM_TYPE_INPUT_CHECKBOX:
|
||||
r.append(element.InitialState)
|
||||
return_vals.append(element.InitialState)
|
||||
elif element.Type == INPUT_RADIO:
|
||||
elif element.Type == ELEM_TYPE_INPUT_RADIO:
|
||||
r.append(element.InitialState)
|
||||
return_vals.append(element.InitialState)
|
||||
elif element.Type == INPUT_COMBO:
|
||||
elif element.Type == ELEM_TYPE_INPUT_COMBO:
|
||||
r.append(element.TextInputDefault)
|
||||
return_vals.append(None)
|
||||
elif element.Type == INPUT_SPIN:
|
||||
elif element.Type == ELEM_TYPE_INPUT_SPIN:
|
||||
r.append(element.TextInputDefault)
|
||||
return_vals.append(None)
|
||||
results.append(r)
|
||||
|
@ -870,39 +908,40 @@ def BuildResults(form):
|
|||
input_values = []
|
||||
for row_num,row in enumerate(form.Rows):
|
||||
for col_num, element in enumerate(row.Elements):
|
||||
if element.Type == INPUT_TEXT:
|
||||
if element.Type == ELEM_TYPE_INPUT_TEXT:
|
||||
value=element.TKStringVar.get()
|
||||
results[row_num][col_num] = value
|
||||
input_values.append(value)
|
||||
elif element.Type == INPUT_CHECKBOX:
|
||||
elif element.Type == ELEM_TYPE_INPUT_CHECKBOX:
|
||||
value=element.TKIntVar.get()
|
||||
results[row_num][col_num] = value
|
||||
input_values.append(value != 0)
|
||||
elif element.Type == INPUT_RADIO:
|
||||
elif element.Type == ELEM_TYPE_INPUT_RADIO:
|
||||
RadVar=element.TKIntVar.get()
|
||||
this_rowcol = EncodeRadioRowCol(row_num,col_num)
|
||||
value = RadVar == this_rowcol
|
||||
results[row_num][col_num] = value
|
||||
input_values.append(value)
|
||||
elif element.Type == BUTTON:
|
||||
elif element.Type == ELEM_TYPE_BUTTON:
|
||||
if results[row_num][col_num] is True:
|
||||
button_pressed_text = element.ButtonText
|
||||
results[row_num][col_num] = False
|
||||
elif element.Type == INPUT_COMBO:
|
||||
elif element.Type == ELEM_TYPE_INPUT_COMBO:
|
||||
value=element.TKStringVar.get()
|
||||
results[row_num][col_num] = value
|
||||
input_values.append(value)
|
||||
elif element.Type == INPUT_SPIN:
|
||||
elif element.Type == ELEM_TYPE_INPUT_SPIN:
|
||||
try:
|
||||
value=element.TKStringVar.get()
|
||||
except:
|
||||
value = 0
|
||||
results[row_num][col_num] = value
|
||||
input_values.append(value)
|
||||
elif element.Type == INPUT_MULTILINE:
|
||||
elif element.Type == ELEM_TYPE_INPUT_MULTILINE:
|
||||
try:
|
||||
value=element.TKText.get(1.0, tk.END)
|
||||
element.TKText.delete('1.0', tk.END)
|
||||
if not form.NonBlocking:
|
||||
element.TKText.delete('1.0', tk.END)
|
||||
except:
|
||||
value = None
|
||||
results[row_num][col_num] = value
|
||||
|
@ -964,7 +1003,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
element_size = (int(element_size[0] * MyFlexForm.Scale[0]), int(element_size[1] * MyFlexForm.Scale[1]))
|
||||
# ------------------------- TEXT element ------------------------- #
|
||||
element_type = element.Type
|
||||
if element_type == TEXT:
|
||||
if element_type == ELEM_TYPE_TEXT:
|
||||
display_text = element.DisplayText # text to display
|
||||
if auto_size_text is False:
|
||||
width, height=element_size
|
||||
|
@ -983,14 +1022,16 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
stringvar.set(display_text)
|
||||
if auto_size_text:
|
||||
width = 0
|
||||
tktext_label = tk.Label(tk_row_frame,anchor=tk.NW, textvariable=stringvar, width=width, height=height, justify=tk.LEFT, bd=border_depth, fg=element.TextColor)
|
||||
justify = tk.LEFT if element.Justification == 'left' else tk.CENTER if element.Justification == 'center' else tk.RIGHT
|
||||
anchor = tk.NW if element.Justification == 'left' else tk.N if element.Justification == 'center' else tk.NE
|
||||
tktext_label = tk.Label(tk_row_frame, textvariable=stringvar, width=width, height=height, justify=justify, bd=border_depth, fg=element.TextColor)
|
||||
# tktext_label = tk.Label(tk_row_frame,anchor=tk.NW, text=display_text, width=width, height=height, justify=tk.LEFT, bd=border_depth)
|
||||
# Set wrap-length for text (in PIXELS) == PAIN IN THE ASS
|
||||
wraplen = tktext_label.winfo_reqwidth() # width of widget in Pixels
|
||||
tktext_label.configure(anchor=tk.NW, font=font, wraplen=wraplen*2 ) # set wrap to width of widget
|
||||
tktext_label.configure(anchor=anchor, font=font, wraplen=wraplen*2 ) # set wrap to width of widget
|
||||
tktext_label.pack(side=tk.LEFT)
|
||||
# ------------------------- BUTTON element ------------------------- #
|
||||
elif element_type == BUTTON:
|
||||
elif element_type == ELEM_TYPE_BUTTON:
|
||||
element.Location = (row_num, col_num)
|
||||
btext = element.ButtonText
|
||||
btype = element.BType
|
||||
|
@ -1019,7 +1060,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
element.TKButton.focus_set()
|
||||
MyFlexForm.TKroot.focus_force()
|
||||
# ------------------------- INPUT (Single Line) element ------------------------- #
|
||||
elif element_type == INPUT_TEXT:
|
||||
elif element_type == ELEM_TYPE_INPUT_TEXT:
|
||||
default_text = element.DefaultText
|
||||
element.TKStringVar = tk.StringVar()
|
||||
element.TKStringVar.set(default_text)
|
||||
|
@ -1031,7 +1072,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
focus_set = True
|
||||
element.TKEntry.focus_set()
|
||||
# ------------------------- COMBO BOX (Drop Down) element ------------------------- #
|
||||
elif element_type == INPUT_COMBO:
|
||||
elif element_type == ELEM_TYPE_INPUT_COMBO:
|
||||
max_line_len = max([len(str(l)) for l in element.Values])
|
||||
if auto_size_text is False: width=element_size[0]
|
||||
else: width = max_line_len
|
||||
|
@ -1041,7 +1082,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
element.TKCombo.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1])
|
||||
element.TKCombo.current(0)
|
||||
# ------------------------- INPUT MULTI LINE element ------------------------- #
|
||||
elif element_type == INPUT_MULTILINE:
|
||||
elif element_type == ELEM_TYPE_INPUT_MULTILINE:
|
||||
default_text = element.DefaultText
|
||||
width, height = element_size
|
||||
element.TKText = tk.scrolledtext.ScrolledText(tk_row_frame, width=width, height=height, wrap='word', bd=border_depth,font=font)
|
||||
|
@ -1053,7 +1094,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
focus_set = True
|
||||
element.TKText.focus_set()
|
||||
# ------------------------- INPUT CHECKBOX element ------------------------- #
|
||||
elif element_type == INPUT_CHECKBOX:
|
||||
elif element_type == ELEM_TYPE_INPUT_CHECKBOX:
|
||||
width = 0 if auto_size_text else element_size[0]
|
||||
default_value = element.InitialState
|
||||
element.TKIntVar = tk.IntVar()
|
||||
|
@ -1061,7 +1102,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
element.TKCheckbutton = tk.Checkbutton(tk_row_frame, anchor=tk.NW, text=element.Text, width=width, variable=element.TKIntVar, bd=border_depth, font=font)
|
||||
element.TKCheckbutton.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1])
|
||||
# ------------------------- PROGRESS BAR element ------------------------- #
|
||||
elif element_type == PROGRESS_BAR:
|
||||
elif element_type == ELEM_TYPE_PROGRESS_BAR:
|
||||
# save this form because it must be 'updated' (refreshed) solely for the purpose of updating bar
|
||||
width = element_size[0]
|
||||
fnt = tkinter.font.Font()
|
||||
|
@ -1079,7 +1120,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
s = ttk.Style()
|
||||
element.TKProgressBar.TKCanvas.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1])
|
||||
# ------------------------- INPUT RADIO BUTTON element ------------------------- #
|
||||
elif element_type == INPUT_RADIO:
|
||||
elif element_type == ELEM_TYPE_INPUT_RADIO:
|
||||
width = 0 if auto_size_text else element_size[0]
|
||||
default_value = element.InitialState
|
||||
ID = element.GroupID
|
||||
|
@ -1097,7 +1138,7 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
variable=element.TKIntVar, value=value, bd=border_depth, font=font)
|
||||
element.TKRadio.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1])
|
||||
# ------------------------- INPUT SPIN Box element ------------------------- #
|
||||
elif element_type == INPUT_SPIN:
|
||||
elif element_type == ELEM_TYPE_INPUT_SPIN:
|
||||
width, height = element_size
|
||||
width = 0 if auto_size_text else element_size[0]
|
||||
element.TKStringVar = tk.StringVar()
|
||||
|
@ -1106,9 +1147,21 @@ def ConvertFlexToTK(MyFlexForm):
|
|||
element.TKSpinBox.configure(font=font) # set wrap to width of widget
|
||||
element.TKSpinBox.pack(side=tk.LEFT, padx=element.Pad[0], pady=element.Pad[1])
|
||||
# ------------------------- OUTPUT element ------------------------- #
|
||||
elif element_type == OUTPUT:
|
||||
elif element_type == ELEM_TYPE_OUTPUT:
|
||||
width, height = element_size
|
||||
element.TKOut = TKOutput(tk_row_frame, width=width, height=height, bd=border_depth)
|
||||
element.TKOut.pack(side=tk.LEFT,padx=element.Pad[0], pady=element.Pad[1])
|
||||
# ------------------------- IMAGE Box element ------------------------- #
|
||||
elif element_type == ELEM_TYPE_IMAGE:
|
||||
photo = tk.PhotoImage(file=element.Filename)
|
||||
if element_size == (None, None) or element_size == None or element_size == MyFlexForm.DefaultElementSize:
|
||||
width, height = photo.width(), photo.height()
|
||||
else:
|
||||
width, height = element_size
|
||||
tktext_label = tk.Label(tk_row_frame, image=photo, width=width, height=height, bd=border_depth)
|
||||
tktext_label.image = photo
|
||||
# tktext_label.configure(anchor=tk.NW, image=photo)
|
||||
tktext_label.pack(side=tk.LEFT)
|
||||
#............................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])
|
||||
|
@ -1619,9 +1672,66 @@ def GetComplimentaryHex(color):
|
|||
comp_color = 0xFFFFFF ^ color
|
||||
# convert the color back to hex by prefixing a #
|
||||
comp_color = "#%06X" % comp_color
|
||||
# return the result
|
||||
return comp_color
|
||||
|
||||
|
||||
|
||||
# ======================== EasyPrint =====#
|
||||
# ===================================================#
|
||||
_easy_print_data = None # global variable... I'm cheating
|
||||
|
||||
class DebugWin():
|
||||
def __init__(self, size=(None, None)):
|
||||
# Show a form that's a running counter
|
||||
win_size = size if size !=(None, None) else DEFAULT_DEBUG_WINDOW_SIZE
|
||||
self.form = FlexForm('Debug Window', auto_size_text=True, font=('Courier New', 12))
|
||||
self.output_element = Output(size=win_size)
|
||||
self.form_rows = [[Text('EasyPrint Output')],
|
||||
[self.output_element],
|
||||
[Quit()]]
|
||||
self.form.AddRows(self.form_rows)
|
||||
self.form.Show(non_blocking=True) # Show a ;non-blocking form, returns immediately
|
||||
return
|
||||
|
||||
def Print(self, *args, end=None, sep=None):
|
||||
sepchar = sep if sep is not None else ' '
|
||||
endchar = end if end is not None else '\n'
|
||||
print(*args, sep=sepchar, end=endchar)
|
||||
# for a in args:
|
||||
# msg = str(a)
|
||||
# print(msg, end="", sep=sepchar)
|
||||
# print(1, 2, 3, sep='-')
|
||||
# if end is None:
|
||||
# print("")
|
||||
self.form.Refresh()
|
||||
|
||||
def Close(self):
|
||||
self.form.CloseNonBlockingForm()
|
||||
self.form.__del__()
|
||||
|
||||
def Print(*args, size=(None,None), end=None, sep=None):
|
||||
EasyPrint(*args, size=size, end=end, sep=sep)
|
||||
|
||||
def PrintClose():
|
||||
EasyPrintClose()
|
||||
|
||||
def eprint(*args, size=(None,None), end=None, sep=None):
|
||||
EasyPrint(*args, size=size, end=end, sep=sep)
|
||||
|
||||
def EasyPrint(*args, size=(None,None), end=None, sep=None):
|
||||
if 'easy_print_data' not in EasyPrint.__dict__: # use a function property to save DebugWin object (static variable)
|
||||
EasyPrint.easy_print_data = DebugWin(size=size)
|
||||
if EasyPrint.easy_print_data is None:
|
||||
EasyPrint.easy_print_data = DebugWin(size=size)
|
||||
EasyPrint.easy_print_data.Print(*args, end=end, sep=sep)
|
||||
|
||||
def EasyPrintClose():
|
||||
if 'easy_print_data' in EasyPrint.__dict__:
|
||||
if EasyPrint.easy_print_data is not None:
|
||||
EasyPrint.easy_print_data.Close()
|
||||
EasyPrint.easy_print_data = None
|
||||
# del EasyPrint.easy_print_data
|
||||
|
||||
# ======================== Scrolled Text Box =====#
|
||||
# ===================================================#
|
||||
def ScrolledTextBox(*args, button_color=None, yes_no=False, auto_close=False, auto_close_duration=None, height=None):
|
||||
|
@ -1733,7 +1843,7 @@ def SetGlobalIcon(icon):
|
|||
# ===================================================#
|
||||
def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), margins=(None,None), element_padding=(None,None),
|
||||
auto_size_text=None, font=None, border_width=None, autoclose_time=None, message_box_line_width=None,
|
||||
progress_meter_border_depth=None):
|
||||
progress_meter_border_depth=None, text_justification=None, debug_win_size=(None,None)):
|
||||
global DEFAULT_ELEMENT_SIZE
|
||||
global DEFAULT_MARGINS # Margins for each LEFT/RIGHT margin is first term
|
||||
global DEFAULT_ELEMENT_PADDING # Padding between elements (row, col) in pixels
|
||||
|
@ -1744,6 +1854,8 @@ def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), ma
|
|||
global DEFAULT_BUTTON_COLOR
|
||||
global MESSAGE_BOX_LINE_WIDTH
|
||||
global DEFAULT_PROGRESS_BAR_BORDER_WIDTH
|
||||
global DEFAULT_TEXT_JUSTIFICATION
|
||||
global DEFAULT_DEBUG_WINDOW_SIZE
|
||||
global _my_windows
|
||||
|
||||
if icon:
|
||||
|
@ -1784,16 +1896,15 @@ def SetOptions(icon=None, button_color=(None,None), element_size=(None,None), ma
|
|||
if progress_meter_border_depth != None:
|
||||
DEFAULT_PROGRESS_BAR_BORDER_WIDTH = progress_meter_border_depth
|
||||
|
||||
if text_justification != None:
|
||||
DEFAULT_TEXT_JUSTIFICATION = text_justification
|
||||
|
||||
if debug_win_size != (None,None):
|
||||
DEFAULT_DEBUG_WINDOW_SIZE = debug_win_size
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# ============================== SetButtonColor =====#
|
||||
# Sets the defaul button color #
|
||||
# ===================================================#
|
||||
def SetButtonColor(foreground, background):
|
||||
global DEFAULT_BUTTON_COLOR
|
||||
|
||||
DEFAULT_BUTTON_COLOR = (foreground, background)
|
||||
|
||||
|
||||
# ============================== sprint ======#
|
||||
|
|
137
readme.md
137
readme.md
|
@ -1,4 +1,4 @@
|
|||
|
||||
[![Downloads](http://pepy.tech/badge/pysimplegui)](http://pepy.tech/project/pysimplegui)
|
||||
# PySimpleGUI
|
||||
|
||||
This really is a simple GUI, but also powerfully customizable.
|
||||
|
@ -9,9 +9,17 @@ This really is a simple GUI, but also powerfully customizable.
|
|||
|
||||
![snap0102](https://user-images.githubusercontent.com/13696193/42781058-1d28d9fa-8913-11e8-847e-5c2afc16ca4c.jpg)
|
||||
|
||||
Add a Progress Meter to your code with ONE LINE of code
|
||||
|
||||
EasyProgressMeter('My meter title', current_value, max value)
|
||||
|
||||
![progress meter 2](https://user-images.githubusercontent.com/13696193/42695896-a37eff5c-8684-11e8-8fbb-3d756655a44b.jpg)
|
||||
|
||||
I was frustrated by having to deal with the dos prompt when I had a powerful Windows machine right in front of me. Why is it SO difficult to do even the simplest of input/output to a window in Python??
|
||||
|
||||
There are a number of 'easy to use' Python GUIs, but they're quite limiting. PySimpleGUI tried to take the best of packages like `EasyGUI`(no longer maintained) and `WxSimpleGUI` (a great package, but was too limited for my application). `PySimpleGUI` provides similar single-call-message-box solutions as you'll see.
|
||||
There are a number of 'easy to use' Python GUIs, but they're **very** limiting. PySimpleGUI takes the best of packages like `EasyGUI`(no longer maintained) and `WxSimpleGUI` (a great package, but limited). The difference between these and PySimpleGUI is that in addition to getting those simple Message Boxes you also get the ability to make your own forms that are highly customizeable.
|
||||
|
||||
Every call has optional parameters so that you can change the look and feel. Don't like the button color? It's easy to change by adding a button_color parameter to your widget.
|
||||
|
||||
GUI Packages with more functionality, like QT and WxPython, require configuring and can take a ***week*** to get *reasonably familiar* with the interfaces.
|
||||
|
||||
|
@ -19,14 +27,6 @@ With a simple GUI, it becomes practical to "associate" .py files with the python
|
|||
|
||||
The `PySimpleGUI` solution is focused on the ***developer***. How can the desired result be achieved in as little and as simple code as possible? This was the mantra used to create PySimpleGUI. How can it be done is a Python-like way?
|
||||
|
||||
You can add a GUI to your command line with a single line of code. With 3 or 4 lines of code you can add a fully customized GUI. And for you Machine Learning folks out there, a **single line** progress meter call that you can drop into any loop to get a graphic like this one:
|
||||
|
||||
|
||||
![progress meter 2](https://user-images.githubusercontent.com/13696193/42695896-a37eff5c-8684-11e8-8fbb-3d756655a44b.jpg)
|
||||
|
||||
|
||||
|
||||
|
||||
Features of PySimpleGUI include:
|
||||
Text
|
||||
Single Line Input
|
||||
|
@ -241,6 +241,12 @@ With a little trickery you can provide a way to break out of your loop using the
|
|||
|
||||
This is the FUN part of the programming of this GUI. In order to really get the most out of the API, you should be using an IDE that supports auto complete or will show you the definition of the function. This will make customizing go smoother.
|
||||
|
||||
This first section on custom forms is for your typical, blocking, non-persistant form. By this I mean, when you "show" the form, the function will not return until the user has clicked a button or closed the window. When this happens, the form's window will be automatically closed.
|
||||
|
||||
Two other types of forms exist.
|
||||
1. Persistent form - rather than closing on button clicks, the show form function returns and the form continues to be visible. This is good for applications like a chat window.
|
||||
2. Asynchronous form - the trickiest of the lot. Great care must be exercised. Examples are an MP3 player or status dashboard. Async forms are updated (refreshed) on a periodic basis.
|
||||
|
||||
It's both not enjoyable nor helpful to immediately jump into tweaking each and every little thing available to you. Let's start with a basic Browse for a file and do something with it.
|
||||
# Copy these design patterns!
|
||||
## Pattern 1 - With Context Manager
|
||||
|
@ -249,7 +255,7 @@ It's both not enjoyable nor helpful to immediately jump into tweaking each and e
|
|||
form_rows = [[SG.Text('SHA-1 and SHA-256 Hashes for the file')],
|
||||
[SG.InputText(), SG.FileBrowse()],
|
||||
[SG.Submit(), SG.Cancel()]]
|
||||
(button, (source_filename, )) = form.LayoutAndShow(form_rows)
|
||||
button, (source_filename, ) = form.LayoutAndShow(form_rows)
|
||||
|
||||
## Pattern 2 - No Context Manager
|
||||
|
||||
|
@ -257,7 +263,7 @@ It's both not enjoyable nor helpful to immediately jump into tweaking each and e
|
|||
form_rows = [[SG.Text('SHA-1 and SHA-256 Hashes for the file')],
|
||||
[SG.InputText(), SG.FileBrowse()],
|
||||
[SG.Submit(), SG.Cancel()]]
|
||||
(button, (source_filename,)) = form.LayoutAndShow(form_rows)
|
||||
button, (source_filename,) = form.LayoutAndShow(form_rows)
|
||||
|
||||
|
||||
These 2 design patters both produce this custom form:
|
||||
|
@ -266,7 +272,7 @@ These 2 design patters both produce this custom form:
|
|||
|
||||
It's important to use the "with" context manager so that resources are freed as quickly as possible, using the currently executing thread. PySimpleGUI uses `tkinter`. `tkinter` is very picky about who releases objects and when. The `with` takes care of disposing of everything properly for you.
|
||||
|
||||
The second design pattern is not context manager based. There are times when the context manager hides errors. If you are struggling with an unknown error, try modifying the code to run without a context manager.
|
||||
The second design pattern is not context manager based. If you are struggling with an unknown error, try modifying the code to run without a context manager. To do so, you simple remove the with, stick the form on the front of that statement, and un-indent the with-block code.
|
||||
|
||||
You will use these design patterns or code templates for all of your "normal" (blocking) types of input forms. Copy it and modify it to suit your needs. This is the quickest way to get your code up and running with PySimpleGUI. This is the most basic / normal of the design patterns.
|
||||
|
||||
|
@ -277,17 +283,17 @@ Going through each line of code in the above form will help explain how to use t
|
|||
with SG.FlexForm('SHA-1 & 256 Hash', auto_size_text=True) as form:
|
||||
This creates a new form, storing it in the variable `form`.
|
||||
|
||||
form_rows = [[SG.Text('SHA-1 and SHA-256 Hashes for the file')],
|
||||
form_rows = [[SG.Text('SHA-1 and SHA-256 Hashes for the file')],
|
||||
The next few rows of code lay out the rows of elements in the window to be displayed. The variable `form_rows` holds our entire GUI window. The first row of this form has a Text element. These simply display text on the form.
|
||||
|
||||
[SG.InputText(), SG.FileBrowse()],
|
||||
[SG.InputText(), SG.FileBrowse()],
|
||||
Now we're on the second row of the form. On this row there are 2 elements. The first is an `Input` field. It's a place the user can enter `strings`. The second element is a `File Browse Button`. A file or folder browse button will always fill in the text field to it's left unless otherwise specified. In this example, the File Browse Button will interact with the `InputText` field to its left.
|
||||
|
||||
[SG.Submit(), SG.Cancel()]]
|
||||
[SG.Submit(), SG.Cancel()]]
|
||||
|
||||
The last line of the `form_rows` variable assignment contains a Submit and a Cancel Button. These are buttons that will cause a form to return its value to the caller.
|
||||
|
||||
(button, (source_filename, )) = form.LayoutAndShow(form_rows)
|
||||
(button, (source_filename, )) = form.LayoutAndShow(form_rows)
|
||||
This is the code that **displays** the form, collects the information and returns the data collected. In this example we have a button return code and only 1 input field
|
||||
|
||||
---
|
||||
|
@ -480,7 +486,16 @@ The most basic element is the Text element. It simply displays text. Many of t
|
|||
size=(None, None),
|
||||
auto_size_text=None,
|
||||
font=None,
|
||||
text_color=None)
|
||||
text_color=None,
|
||||
justification=None)
|
||||
.
|
||||
|
||||
Text - The text that's displayed
|
||||
size - Element's size
|
||||
auto_size_text - Bool. Change width to match size of text
|
||||
font - Font name and size to use
|
||||
text_color - text color
|
||||
justification - Justification for the text. String - 'left', 'right', 'center'
|
||||
|
||||
Some commonly used elements have 'shorthand' versions of the functions to make the code more compact. The functions `T` and `Txt` are the same as calling `Text`.
|
||||
|
||||
|
@ -814,7 +829,7 @@ Each of the tabs of the form is in fact a form. The same steps are taken to cre
|
|||
|
||||
## Global Settings
|
||||
**Global Settings**
|
||||
You can set the global settings using the function `PySimpleGUI.SetOptions`. Each option has an optional parameter that's used to set it.
|
||||
Let's have some fun customizing! Make PySimpleGUI look the way you want it to look. You can set the global settings using the function `PySimpleGUI.SetOptions`. Each option has an optional parameter that's used to set it.
|
||||
|
||||
SetOptions(icon=None,
|
||||
button_color=(None,None),
|
||||
|
@ -825,7 +840,8 @@ You can set the global settings using the function `PySimpleGUI.SetOptions`. Ea
|
|||
font=None, border_width=None,
|
||||
autoclose_time=None,
|
||||
message_box_line_width=None,
|
||||
progress_meter_border_depth=None):
|
||||
progress_meter_border_depth=None,
|
||||
text_justification=None):
|
||||
|
||||
Explanation of parameters
|
||||
|
||||
|
@ -840,6 +856,7 @@ Explanation of parameters
|
|||
autoclose_time - time in seconds for autoclose boxes
|
||||
message_box_line_width - number of characers in a line of text in message boxes
|
||||
progress_meter_border_depth - amount of border around raised or lowered progress meters
|
||||
text_justification - justification to use on Text Elements. Values are strings - 'left', 'right', 'center'
|
||||
|
||||
|
||||
These settings apply to all forms `SetOptions`. The Row options and Element options will take precedence over these settings. Settings can be thought of as levels of settings with the Form-level being the highest and the Element-level the lowest. Thus the levels are:
|
||||
|
@ -851,7 +868,55 @@ These settings apply to all forms `SetOptions`. The Row options and Element opt
|
|||
Each lower level overrides the settings of the higher level
|
||||
|
||||
## Asynchronous (Non-Blocking) Forms
|
||||
While the majority of GUIs are a simple exercise to "collect input values and return with them", there are instances where we want to continue executing while the form is open. These are "asynchronous" forms and require special options, new SDK calls, and **great care**.
|
||||
So you want to be a wizard do ya? Well go boldly! While the majority of GUIs are a simple exercise to "collect input values and return with them", there are instances where we want to continue executing while the form is open. These are "asynchronous" forms and require special options, new SDK calls, and **great care**. With asynchronous forms the form is shown, user input is read, but your code keeps right on chugging. YOUR responsibility is to call `PySimpleGUI.refresh` on a periodic basis. Once a second or more will produce a reasonably snappy GUI.
|
||||
When do you use a non-blocking form? A couple of examples are
|
||||
* A media file player like an MP3 player
|
||||
* A status dashboard that's periodically updated
|
||||
* Progress Meters - when you want to make your own progress meters
|
||||
* Output using print to a scrolled text element. Good for debugging.
|
||||
|
||||
We're going to build an app that does the latter. It's going to update our form with a running clock.
|
||||
|
||||
The basic flow and functions you will be calling are:
|
||||
Setup
|
||||
|
||||
form = FlexForm()
|
||||
form.AddRows(form_rows)
|
||||
form.Show(non_blocking = True)
|
||||
|
||||
Periodic refresh
|
||||
|
||||
form.Refresh()
|
||||
If you need to close the form
|
||||
|
||||
form.CloseNonBlockingForm()
|
||||
|
||||
Rather than the usual `form.LayoutAndShow()` call, we're manually adding the rows (doing the layout) and then showing the form. After the form is shown, you simply call `form.Refresh()` every now and then.
|
||||
|
||||
When you are ready to close the form (assuming the form wasn't closed by the user or a button click) you simply call `form.CloseNonBlockingForm()`
|
||||
|
||||
**Example - Running timer that updates**
|
||||
We're going to make a form and update one of the elements of that form every .01 seconds. Here's the entire code to do that.
|
||||
|
||||
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()
|
||||
What we have here is the same sequence of function calls as in the description. Get a form, add rows to it, show the form, and then refresh it every now and then.
|
||||
The new thing in this example is the call use of the Update method for the Text Element. The first thing we do inside the loop is "update" the text element that we made earlier. This changes the value of the text field on the form. The new value will be displayed when `form.Refreshu()` is called. That's it... this example follows the async design pattern well.
|
||||
|
||||
|
||||
|
||||
## Sample Applications
|
||||
|
@ -874,12 +939,14 @@ sprint
|
|||
Call `sprint` with as many parameters as you want and it'll print them all out in a `ScrolledTextBox`. This is simply a function pointing to `PySimpleGUI.ScrolledTextBox`.
|
||||
|
||||
---
|
||||
## Known Issues
|
||||
# Known Issues
|
||||
While not an "issue" this is a ***stern warning***
|
||||
|
||||
## **Do not attempt** to call `PySimpleGUI` from multiple threads! It's `tkinter` based and `tkinter` has issues with multiple threads
|
||||
|
||||
**Progress Meters** - the visual graphic portion of the meter may be off. May return to the native tkinter progress meter solution in the future. Right now a "custom" progress meter is used. On the bright side, the statistics shown are extremely accurate and can tell you something about the performance of your code.
|
||||
**Async Forms** - these include the 'easy' forms (EasyProgressMeter and EasyPrint/Print). If you start overlapping having Async forms open with normal forms then things get a littler squirrelly. Still tracking down the issues and am making it more solid every day possible. You'll know there's an issue when you see blank form.
|
||||
**EasyPrint** - EasyPrint is a new feature that's pretty awesome. You print and the output goes to a window, with a scroll bar, that you can copy and paste from. Being a new feature, it's got some potential problems. There are known interaction problems with other GUI windows. For example, closing a Print window can also close other windows you have open. For now, don't close your debug print window until other windows are closed too.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -892,6 +959,7 @@ A MikeTheWatchGuy production... entirely responsible for this code.... unless it
|
|||
| 1.0.21 | July 13, 2018 - Readme updates |
|
||||
| 2.0.0 | July 16, 2018 - ALL optional parameters renamed from CamelCase to all_lower_case
|
||||
| 2.1.1 | July 18, 2018 - Global settings exposed, fixes
|
||||
| 2.2.0| July 20, 2018 - Image Elements, Print output
|
||||
|
||||
## Code Condition
|
||||
|
||||
|
@ -907,7 +975,7 @@ While the internals to PySimpleGUI are a tad sketchy, the public interfaces into
|
|||
MikeTheWatchGuy
|
||||
|
||||
## License
|
||||
|
||||
|
||||
This project is limited to non-commercial applications. If you wish to use it commercially, please contact one of the authors.
|
||||
For non-commercial individual, the GNU Lesser General Public License (LGPL 3) applies.
|
||||
|
||||
|
@ -915,7 +983,28 @@ For non-commercial individual, the GNU Lesser General Public License (LGPL 3) a
|
|||
|
||||
* Jorj McKie was the motivator behind the entire project. His wxsimpleGUI concepts sparked PySimpleGUI into existence
|
||||
|
||||
|
||||
## How Do I
|
||||
Finally, I must thank the fine folks at How Do I.
|
||||
https://github.com/gleitz/howdoi
|
||||
Their utility has forever changed the way and pace in which I can program. I urge you to try the HowDoI.py application here on GitHub. Trust me, **it's going to be worth the effort!**
|
||||
Here are the steps to run that application
|
||||
|
||||
Install howdoi:
|
||||
pip install howdoi
|
||||
Test your install:
|
||||
python -m howdoi howdoi.py
|
||||
To run it:
|
||||
Python HowDoI.py
|
||||
|
||||
The pip command is all there is to the setup.
|
||||
|
||||
The way HowDoI works is that it uses your search term to look through stack overflow posts. It finds the best answer, gets the code from the answer, and presents it as a response. It gives you the correct answer OFTEN. It's a miracle that it work SO well.
|
||||
For Python questions, I simply start my query with 'Python'. Let's say you forgot how to reverse a list in Python. When you run HowDoI and ask this question, this is what you'll see.
|
||||
![snap0109](https://user-images.githubusercontent.com/13696193/42916444-4199b16c-8ad3-11e8-8423-d12e61a58d3d.jpg)
|
||||
|
||||
In the hands of a competent programmer, this tool is **amazing**. It's a must-try kind of program that has completely changed my programming process. I'm not afraid of asking for help! You just have to be smart about using what you find.
|
||||
|
||||
The PySimpleGUI window that the results are shown in is an 'input' field which means you can copy and paste the results right into your code.
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue