jason990420 / pysimplegui-solution Goto Github PK
View Code? Open in Web Editor NEWSome issues about using PySimpleGUI
Some issues about using PySimpleGUI
Themes - Automatic Coloring of Your Windows
Default colors,
table.tag_configure("DEFAULT", foreground=sg.theme_text_color(), background=sg.theme_background_color())
from time import sleep
import PySimpleGUI as sg
def click(element):
element.Widget.config(relief = "sunken")
window.refresh()
sleep(0.1)
element.Widget.config(relief = "raised")
window.refresh()
element.click()
sg.theme('DarkAmber')
layout = [
[sg.Text('Some text on Row 1')],
[sg.Text('Enter something on Row 2'), sg.InputText()],
[sg.Button('Ok'), sg.Button('Cancel')]
]
window = sg.Window('Window Title', layout, finalize=True)
window.bind('<Key-F3>', 'F3')
window.bind('<Key-F5>', 'F5')
ok = window['Ok']
cancel = window['Cancel']
while True:
event, values = window.read()
print(event)
if event == sg.WIN_CLOSED or event == 'Cancel':
break
if event == 'F3':
# click(ok)
ok.click()
elif event == 'F5':
# click(cancel)
cancel.click()
window.close()
import PySimpleGUI as sg
def hide_header(tree):
tree.Widget.configure(show='tree')
def key_to_id(key):
for k, v in tree.IdToKey.items():
if v == key:
return k
return None
def select(key=''):
iid = key_to_id(key)
if iid:
tree.Widget.see(iid)
tree.Widget.selection_set(iid)
def where():
item = tree.Widget.selection()
return '' if len(item) == 0 else tree.IdToKey[item[0]]
def move_up():
key = where()
if key == '':
return
node = treedata.tree_dict[key]
parent_node = treedata.tree_dict[node.parent]
index = parent_node.children.index(node)
if index != 0:
parent_node.children[index-1], parent_node.children[index] = (
parent_node.children[index], parent_node.children[index-1])
tree.update(values=treedata)
select(key)
def move_down():
key = where()
if key == '':
return
node = treedata.tree_dict[key]
parent_node = treedata.tree_dict[node.parent]
index = parent_node.children.index(node)
if index != len(parent_node.children)-1:
parent_node.children[index+1], parent_node.children[index] = (
parent_node.children[index], parent_node.children[index+1])
tree.update(values=treedata)
select(key)
fruits = [
"Apple", "Banana", "Cherry", "Durian", "Elderberry", "Guava", "Jackfruit",
"Kiwi", "Lemon", "Mango", "Orange", "Papaya", "Strawberry", "Tomato",
"Watermelon",
]
treedata = sg.TreeData()
for i, fruit in enumerate(fruits):
treedata.Insert('', i, fruit, values=[f'Fruit {i:0>2d}'])
layout = [
[sg.Button('Move Up'), sg.Button('Move Down')],
[sg.Tree(data=treedata, key='TREE', headings=['Nothing'],
select_mode=sg.TABLE_SELECT_MODE_BROWSE)],
]
window = sg.Window('Tree', layout, finalize=True)
tree = window['TREE']
hide_header(tree)
while True:
event, values = window.read()
print(event, values)
if event == sg.WINDOW_CLOSED:
break
elif event == 'Move Up':
move_up()
elif event == 'Move Down':
move_down()
window.close()
import ctypes
from inspect import getfullargspec, signature
import PySimpleGUI as sg
def Tree(key, treedata, font=('Courier New', 12, 'bold'), width=22):
return sg.Tree(data=treedata, headings=['Notes',], pad=(1, 0),
show_expanded=True, col0_width=width, auto_size_columns=False,
visible_column_map=[False,], select_mode=sg.TABLE_SELECT_MODE_BROWSE,
enable_events=True, text_color=None, background_color=None,
font=font, num_rows=20, row_height=26, key=key)
def get_new_key(treedata):
i = 0
while i in treedata.tree_dict:
i += 1
return i
def update_attributes(element, tree_attribute):
item = getfullargspec(element)
args = item.args[1:]
defaults = item.defaults
treedata = sg.TreeData()
text_list = sorted([f'{arg:35}= {repr(default):30}' for arg, default in
zip(args, defaults)], key=lambda x:(not x.islower(), x))
for text in text_list:
treedata.insert('', get_new_key(treedata), text, [])
tree_attribute.update(values=treedata)
return treedata
def update_methods(element, tree_methods):
methods = []
names = []
for name in sorted(dir(element), key=lambda x:(not x.islower(), x)):
attr = getattr(element, name)
if not name.startswith('_') and attr not in methods and callable(attr):
names.append(name)
methods.append(attr)
treedata = sg.TreeData()
for text in names:
treedata.insert('', get_new_key(treedata), text, [])
tree_methods.update(values=treedata)
return treedata
element_classes = sg.Element.__subclasses__()
elements = {element.__name__: element for element in element_classes}
elements['Window'] = sg.Window
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
sg.theme('DarkBlue')
treedata_element = sg.TreeData()
treedata_attribute = sg.TreeData()
treedata_method = sg.TreeData()
for item in sorted(elements.keys()):
treedata_element.insert('', get_new_key(treedata_element), item, [])
layout = [
[
Tree('TREE_ELEMENT', treedata_element),
Tree('TREE_ATTRIBUTE', treedata_attribute, width=63),
Tree('TREE_METHOD', treedata_method, width=35),
],
[
sg.Multiline('', font=('Courier New', 12, 'bold'), pad=(1, 4),
size=(124, 10), background_color='DarkBlue', key='STATUS'),
]
]
window = sg.Window("PySimpleGUI Helper", layout, finalize=True)
tree_element = window['TREE_ELEMENT']
tree_attribute = window['TREE_ATTRIBUTE']
tree_method = window['TREE_METHOD']
status = window['STATUS']
tree_element.Widget.heading("#0", text='PySimleGUI Elements')
tree_attribute.Widget.heading("#0", text='Arguments To Initialize & Default Value')
tree_method.Widget.heading("#0", text='Element Methods')
for key, element in window.AllKeysDict.items(): # Remove focus
element.Widget.configure(takefocus=0)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event == 'TREE_ELEMENT':
key = values[event][0]
element_text = treedata_element.tree_dict[key].text
element = elements[element_text]
treedata_attribute = update_attributes(element, tree_attribute)
treedata_method = update_methods(element, tree_method)
status.update(value='')
elif event == 'TREE_METHOD':
key = values[event][0]
text = treedata_method.tree_dict[key].text
method = getattr(element, text)
string = '\n'.join(map(str.strip, method.__doc__.split('\n')))
status.update(value='def '+text+str(signature(method))+'\n'+string)
window.close()
from datetime import datetime
from random import randint
import PySimpleGUI as sg
class Auto_Input(sg.Input):
user_event = True
window = None
win, element = None, None
dx, dy = 0, 0
def __init__(self, **kw):
super().__init__(**kw)
def binding(self):
self.bind('<FocusIn>', '+FocusIn')
self.bind('<FocusOut>', '+FocusOut')
def write_value(self, text):
self.update(text)
self.set_focus()
Auto_Input.user_event = False
def auto_complete(self, text):
Auto_Input.element = self
if not Auto_Input.user_event:
Auto_Input.user_event = True
return
if text:
widget = self.Widget
width, height = widget.winfo_width(), widget.winfo_height()
x, y = widget.winfo_rootx(), widget.winfo_rooty()
text_list = [f'{text}{i}' for i in range(10)]
location = (x, y+height+2)
if Auto_Input.win and Auto_Input.win.TKroot:
Auto_Input.win.close()
self.listbox(text_list, location)
self.set_focus()
Auto_Input.user_event = False
else:
if Auto_Input.win:
Auto_Input.win.close()
def listbox(self, text_list, location):
x, y = self.Size
layout = [[sg.Listbox(values=text_list, size=(x, 5), pad=(0, 0),
enable_events=True, bind_return_key=True, key='Auto')]]
Auto_Input.win = sg.Window("", layout, default_element_size=(10, 1),
margins=(0, 0), auto_size_text=False, return_keyboard_events=True,
keep_on_top=True, no_titlebar=True, auto_size_buttons=False,
location=location, finalize=True)
root1 = Auto_Input.window.TKroot
root2 = Auto_Input.win.TKroot
x1, y1 = root1.winfo_x(), root1.winfo_y()
x2, y2 = root2.winfo_x(), root2.winfo_y()
Auto_Input.dx, Auto_Input.dy = x2-x1, y2-y1
def follow(event):
if Auto_Input.win and Auto_Input.win.TKroot:
root = Auto_Input.window.TKroot
x, y = root.winfo_x(), root.winfo_y()
Auto_Input.win.TKroot.geometry(
f"+{x+Auto_Input.dx}+{y+Auto_Input.dy}")
def focus_binding(window):
Auto_Input.window = window
Auto_Input.window.TKroot.bind("<Configure>", Auto_Input.follow)
for element in window.AllKeysDict.values():
if isinstance(element, Auto_Input):
element.binding()
def main():
sg.theme("DarkBlue")
sg.set_options(font=("Courier New", 12))
layout = [[Auto_Input(size=(randint(10, 20), 1), enable_events=True,
key=f'INPUT,{i},{j}') for j in range(4)] for i in range(3)]
window = sg.Window("Title", layout, finalize=True)
focus_binding(window)
while True:
win, event, values = sg.read_all_windows()
print(event)
if event == sg.WINDOW_CLOSED:
break
elif event.startswith('INPUT') and not event.endswith('FocusOut'):
key = event.split("+")[0]
element, text = window[key], values[key]
element.auto_complete(text)
elif event.endswith('FocusOut'):
if Auto_Input.win and Auto_Input.win.TKroot:
Auto_Input.win.hide()
elif event == 'Auto':
if values['Auto'] and Auto_Input.element:
text = values['Auto'][0]
Auto_Input.element.write_value(text)
if Auto_Input.win:
Auto_Input.win.close()
window.close()
if __name__ == '__main__':
main()
There're three places to set the focus box.
focus
of sg.Button
: if True
, initial focus will be put on this button, default value is False
.use_default_focus
of sg.Window
: If True will use the default focus algorithm to set the focus to the "Correct" elementblock_focus(block=True)
of element: If True
, this element will not be given focus by using the keyboard (TAB or ctrl-TAB) to go from one element to another.To remove focus box in sg.Button
,
focus=False
in sg.Button
, it is defaultuse_default_focus=False
in sg.Window
(It looks like there's no difference now)block_focus()
of element sg.Button
after window finalized.import PySimpleGUI as sg
sg.theme('DarkAmber')
layout = [
[sg.Button('tk Button'),sg.Button('ttk Button', use_ttk_buttons=True)],
[sg.Input()],
[sg.Input()],
]
window = sg.Window('Window Title', layout, finalize=True)
for element in window.key_dict.values():
if isinstance(element, sg.Button):
element.block_focus()
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
window.close()
There's no option to wrap text in sg.Tree
or ttk.Treeview
.
As I know, the only way is to insert '\n' into your text, then it will wrap to next line.
To show all lines in a row, you have to set option row_height
large enough to fit all lines.
Here, another problem will be generated for row_height
.
row_height
large enough, you will still see same space occupied if only one line.It seems there's no way to set row height for different rows.
Here's example code about how I do it.
import textwrap
from random import randint, choice
import PySimpleGUI as sg
def wrap(string, lenght=20):
return '\n'.join(textwrap.wrap(string, lenght))
sg.theme('DarkBlue')
sg.set_options(font=('Courier New', 12))
treedata = sg.TreeData()
for i in range(10):
parent = choice(list(treedata.tree_dict.keys()))
values = [' '.join([f'This is Text {j}' for j in range(1, randint(2, 4))])]
treedata.insert(parent, i, f'Node {i}', values=list(map(wrap, values)))
layout = [
[sg.Tree(data=treedata, headings=['Data',], auto_size_columns=False,
num_rows=10, col0_width=20, col_widths=[20], key='-TREE-',
row_height=18*3, justification='left', show_expanded=True,
enable_events=True),],
]
window = sg.Window('Tree Element Test', layout, finalize=True)
tree = window['-TREE-'].Widget
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Cancel'):
break
window.close()
Another way is more difficult to implement, using other elements to simulate a tree or a table.
I have nothing for it now.
import PySimpleGUI as sg
biglist = [
('Abundant Life','John 10:10'),
('Angels','Psalm 103:20'),
('Boldness','Proverbs 28:1')
]
attrib_dict = {key:value for key, value in biglist}
attrib_list = sorted(attrib_dict.keys())
sg.theme('Tan Blue')
sg.set_options(font=('Courier New', 12))
layout = [
[sg.Text('Pick an attribute and see verse of promise.')],
[sg.Listbox(attrib_list, size=(20, 10), key='-LIST-', enable_events=True),
sg.Multiline(size = (60, 10), key = '-MULTILINE-', autoscroll = False, disabled=True)],
[sg.Button('Exit')]]
window = sg.Window("God's Promises", layout, finalize=True)
window['-MULTILINE-'].Widget.configure(spacing1=1) # tkinter code here
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
elif event == '-LIST-':
selection = values[event][0]
window['-MULTILINE-'].update(value=attrib_dict[selection])
window.close()
Following code show my way for how to expand or collapse node in Tree, also show what node expanded with what text, you can also change it to with what key to avoid duplicates.
import PySimpleGUI as sg
class Tree_Data():
def __init__(self):
self.treedata = sg.TreeData()
def new_key(self):
i = 0
while i in self.treedata.tree_dict:
i += 1
return i
def insert(self, data):
for i, first in enumerate(data):
key = self.new_key()
self.treedata.Insert('', key, first[0], values=[])
for j, second in enumerate(first[1]):
self.treedata.Insert(key, self.new_key(), second, values=[])
def get_expanded():
opens = []
for id_, key in tree.IdToKey.items(): # id_ of item for tkinter , key of item for PySimpleGUI
item = tree.Widget.item(id_)
if item['open']: # Keys of item: 'image', 'open', 'tags', 'text', 'values'
opens.append(item['text'])
return opens
def key_to_id(key):
return tree.KeyToID[key] if key in tree.KeyToID else None
def expand_all():
for key in tree_data.treedata.tree_dict:
tree.Widget.item(key_to_id(key), open=True)
def collapse_all():
for key in tree_data.treedata.tree_dict:
tree.Widget.item(key_to_id(key), open=False)
sg.theme('dark')
sg.set_options(font=('Helvetica', 10))
tree_data = Tree_Data()
data = [
['Fruit', ['Apple', 'Mongo']],
['Seasoning', ['Cat', 'Dog', 'Tiger']],
['Meat', ['Pork', 'Beef', 'Fish']],
['Vegetable', ['Broccoli', 'Cabbages', 'Mushrooms']],
]
tree_data.insert(data)
layout = [
[sg.Tree(data=tree_data.treedata, headings=['Notes',], show_expanded=False,
col0_width=20, select_mode=sg.TABLE_SELECT_MODE_BROWSE, enable_events=True,
visible_column_map=[False,], key='TREE')],
[sg.Button('Expand'), sg.Button('Collapse')]
]
window = sg.Window('TREE', layout, finalize=True)
tree = window['TREE']
tree.Widget.configure(show='tree') # Hide header
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event)
if event == 'Expand':
expand_all()
elif event == 'Collapse':
collapse_all()
print(get_expanded())
window.close()
TREE
['Seasoning']
TREE
['Seasoning', 'Vegetable']
It is necessary to explain some arguments about the column width in sg.Table
auto_size_columns
max_col_width
, andcol_widths
, if None, defined by def_col_width
Actually, horizontal scrollbar is useless for table because table always keep the width as sum of widths of all columns.
So all content of table show and that's why horizontal scrollbar is greyed out.
There's no way to work under PySimpleGUI, so the only way is to use tkinter code.
Here's example to show how it work.
from tkinter.font import Font
import numpy as np
import PySimpleGUI as sg
sg.theme('DarkBlue')
font_family, font_size = font = ('Courier New', 11)
sg.set_options(font=font)
headings = ['ID', 'Description']
data = [['ID', ' '.join([f'({i},{j})' for j in range(100)])] for i in range(100)]
frm_input_layout = [
[sg.Table(values=data, headings=headings, justification='left',
enable_events=True, key='tbl_variants',
auto_size_columns=False, col_widths=[2, 40], # Set width of window: 2+40 = 42 chars only
hide_vertical_scroll=False, vertical_scroll_only=False,
)]
]
layout = [[sg.Frame('Input', frm_input_layout)]]
window = sg.Window('App', layout, finalize=True)
# Set real table width after here
window.TKroot.update()
tree = window['tbl_variants'].Widget
tkfont = Font(family=font_family, size=font_size)
data_array = np.array([headings] + data)
column_widths = [max(map(lambda item:tkfont.measure(item), data_array[:, i]))
for i in range(data_array.shape[1])]
for heading, width in zip(headings, column_widths):
tree.column(heading, width=width+font_size)
# Set real table width before here
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
window.close()
import PySimpleGUI as sg
sg.theme("DarkBlue")
sg.set_options(font=('Courier New', 12))
input_size1, input_size2 = (34, 1), (10, 1)
frame_filename = [
[sg.Text("Filename")],
[sg.pin(sg.Column([[sg.Input("*.ppt", size=input_size1)]], pad=(0, 0)))]
]
frame_directory = [
[sg.Checkbox("In directory", enable_events=True, key='col1')],
[sg.pin(sg.Column(
[[sg.Input("D:\\", size=input_size1)]], pad=(0,0), key='COL1'))],
]
frame_content = [
[sg.Checkbox("In contents", enable_events=True, key='col2')],
[sg.pin(sg.Column(
[[sg.Input("American", size=input_size1)]], pad=(0,0), key='COL2'))],
]
frame_modify = [
[sg.Checkbox("Modified between", enable_events=True, key='col3')],
[sg.pin(sg.Column(
[[sg.Input("2021/01/01", size=input_size2, key='IN1'), sg.Text("-"),
sg.Input("2021/01/14", size=input_size2, key='IN2')]],
pad=(0,0), key='COL3'))],
]
unit = ["B", "KB", "MB", "GB"]
frame_size_range = [
[sg.Checkbox("Size range", enable_events=True, key='col4')],
[sg.pin(sg.Column(
[[sg.Input("500", size=input_size2, key='IN3'), sg.Text("-"),
sg.Input("1000", size=input_size2, key='IN4'),
sg.Combo(unit, unit[1], size=(4, 1), readonly=True)]],
pad=(0,0), key='COL4'))]
]
frame_parameters = [
[sg.Frame('', frame_filename, key='FRAME1')],
[sg.Frame('', frame_directory, key='FRAME2')],
[sg.Frame('', frame_content, key='FRAME3')],
[sg.Frame('', frame_modify, key='FRAME4')],
[sg.Frame('', frame_size_range, key='FRAME5')],
]
frame_main_search = [
[sg.Input("*.pdf", size=input_size1, key='IN5')],
[sg.Checkbox("All"), sg.Checkbox("Directory"), sg.Checkbox("Document"),
sg.Checkbox("Executable"), sg.Checkbox("Picture"), sg.Checkbox("Music"),
sg.Checkbox("Video")],
[sg.Multiline("No files", border_width=5, key='ML')],
]
frame_top = [
[sg.Frame('',
[[sg.Frame('', frame_parameters, pad=(0, 0), vertical_alignment='top',
border_width=0, key='FRAME6')]],
vertical_alignment='top', pad=(0, 0), border_width=0),
sg.Frame('', frame_main_search, pad=(0, 0), border_width=0, key='FRAME7')],
]
layout = [
[sg.Frame('', frame_top, pad=(0, 0), border_width=0,
vertical_alignment='top', key='FRAME8')],
[sg.Frame('', [[sg.StatusBar("Status: OK", pad=(0, 0))]], pad=(0, 0),
vertical_alignment='top', border_width=0, key='FRAME9')],
]
window = sg.Window("Title", layout, size=(1200, 500), resizable=True,
finalize=True)
# Expand element as window resize, it will always make element visible
for key in ['IN5', 'FRAME2', 'FRAME3', 'FRAME4', 'FRAME5', 'FRAME9']:
window[key].expand(expand_x=True, expand_row=False)
for key in ['FRAME6']:
window[key].expand(expand_y=True, expand_row=False)
for key in ['ML', 'FRAME7', 'FRAME8']:
window[key].expand(expand_x=True, expand_y=True)
columns = [f'col{i+1}' for i in range(4)]
# Hide some unchecked elements
for col in columns:
window[col.upper()].update(visible=False)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
if event in columns:
toggle = values[event]
window[event.upper()].update(visible=toggle)
window.close()
iid
string start from '1', not '0'. method move
index from 0.
if event == 'Delete':
for iid in ('5', '6', '7'):
if table.exists(iid):
table.delete(iid)
window['-TABLE-'].tree_ids.remove(iid)
if event == 'Detach':
for iid in ('2', '3'):
table.detach(iid)
if event == 'Reattach':
for iid in ('2', '3'):
table.move(iid, '', int(iid)-1)
if event == 'Empty':
window['-TABLE-'].update([])
import pickle
from random import randint
import PySimpleGUI as sg
class Progress_Bar():
data = None
def __init__(self, graph, filename, text_color='white',
font=('Courier New', 12)):
self.graph = graph
if Progress_Bar.data is None:
with open (filename, 'rb') as file:
Progress_Bar.data = pickle.load(file)
self.text_color = text_color
self.font = font
self.p, self.t = None, None
self.initial()
def initial(self, angle=0):
self.circle = self.graph.draw_image(
data=Progress_Bar.data[361], location=(-50, 50))
self.set_now(angle)
self.set_target(angle)
def set_target(self, angle=0, step=10):
self.target = min(360, max(0, int(angle)))
self.step = min(360, max(1, int(step)))
def set_now(self, angle=0):
self.angle = min(360, max(0, int(angle)))
def move(self):
if self.target == self.angle:
return True
if self.angle < self.target:
self.angle = min(self.target, self.angle+self.step)
else:
self.angle = max(self.target, self.angle-self.step)
if self.p:
self.graph.delete_figure(self.p)
if self.t:
self.graph.delete_figure(self.t)
text = f'{self.angle/3.6:.1f}%'
self.p = self.graph.draw_image(data=Progress_Bar.data[self.angle],
location=(-50, 50))
self.t = self.graph.draw_text(text, (0, 0), color=self.text_color,
font=self.font, text_location=sg.TEXT_LOCATION_CENTER)
return False
rows, columns = 4, 6
layout = [[sg.Graph((111, 111), (-55, -55), (55, 55), background_color='green',
key=f'Graph ({j}, {i})') for i in range(columns)] for j in range(rows)]
window = sg.Window('Circular Progressbar', layout, finalize=True)
filename = 'D:/arc_png.dat'
progress_bar = [[Progress_Bar(layout[row][column], filename)
for column in range(columns)] for row in range(rows)]
for row in range(rows):
for column in range(columns):
progress_bar[row][column].set_target(randint(0, 360))
while True:
event, values = window.read(timeout=5)
if event == sg.WINDOW_CLOSED:
break
elif event == '__TIMEOUT__':
for row in range(rows):
for column in range(columns):
p = progress_bar[row][column]
if p.move():
p.set_target(randint(0, 360))
window.close()
Create 'D:/arc_png.dat' file
import math
import pickle
from io import BytesIO
from PIL import Image, ImageColor
class Point(tuple):
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
@property
def x(self):
return self[0]
@property
def y(self):
return self[1]
@property
def angle(self):
degree = math.atan2(self[1], self[0])*180/math.pi
if degree < 0:
degree += 360
return degree
@property
def distance(self):
return (self[0]**2 + self[1]**2)**0.5
def __add__(self, other):
if isinstance(other, (Point, tuple)):
return Point(self[0]+other[0], self[1]+other[1])
raise ValueError
def __repr__(self):
return f'({self.x}, {self.y})'
class Arc():
def __init__(self, width=101, radius=40, start_angle=0, stop_angle=360,
color='yellow', thickness=20):
self.width = width
self.half = self.width//2
self.radius = radius
self.start_angle = self.limit(start_angle)
self.stop_angle = self.limit(self.start_angle + self.limit(
stop_angle - self.start_angle))
self.color = self.color_to_rgb(color)
self.radius_inner = radius - thickness//2
self.radius_outer = radius + thickness//2
def limit(self, degree):
return min(360, max(0, degree))
def color_to_rgb(self, color):
if isinstance(color, str):
if color.startswith('#'):
return tuple(bytes.fromhex(color[1:]))
elif color in ImageColor.colormap:
return ImageColor.getcolor(color, 'RGB')
elif isinstance(color, tuple):
return color
raise ValueError
def check(self, point):
if ((-self.half <= point.x <= self.half) and
-self.half <= point.y <= self.half):
angle = point.angle
alpha_1 = 1
if (self.start_angle-1 < angle < self.stop_angle+1):
if angle < self.start_angle:
alpha_1 = 1 - self.start_angle + angle
elif angle > self.stop_angle:
alpha_1 = 1 - angle + self.stop_angle
else:
return
distance = point.distance
alpha_2 = 1
if (self.radius_inner-1 < distance < self.radius_outer+1):
if distance < self.radius_inner:
alpha_2 = 1 - self.radius_inner + distance
elif distance > self.radius_outer:
alpha_2 = 1 - distance + self.radius_outer
else:
return
return int(alpha_1*alpha_2*255)
return
@property
def data(self):
r, g, b = self.color
checked = []
im = Image.new('RGBA', (self.width, self.width), (255, 255, 255, 0))
radian = self.start_angle/180*math.pi
to_check = [Point(
int(self.radius_inner*math.cos(radian)),
int(self.radius_inner*math.sin(radian)))]
while to_check:
tmp = []
for p in to_check:
alpha = self.check(p)
checked.append(p)
if alpha is not None:
x, y = p.x + self.half, self.half-p.y
im.putpixel((x, y), (r, g, b, alpha))
for dp in (Point(1, 0), Point(-1, 0), Point(0, 1),
Point(0, -1)):
new_point = p + dp
if ((new_point not in checked)
and (new_point not in to_check)
and (new_point not in tmp)):
tmp.append(new_point)
to_check = list(set(tmp))
with BytesIO() as output:
im.save(output, format="PNG")
data = output.getvalue()
return data
frames = []
for angle in range(361):
print(angle)
frames.append(Arc(stop_angle=angle, color='blue').data)
frames.append(Arc(stop_angle=360, color='yellow').data)
filename = 'D:/arc_png.dat'
with open(filename, 'wb') as file:
pickle.dump(frames, file)
Before resize,
After resized
import PySimpleGUI as sg
sg.theme("DarkBlue")
sg.set_options(font=('Courier New', 16))
layout = [
[sg.Table([[f'Item {i}'] for i in range(10)], headings=["Item"],
justification='center', auto_size_columns=False, col_widths=[20],
num_rows=10, key='table'),
sg.Multiline("", size=(20, 5), key='multiline'),
]
]
window = sg.Window("Title", layout, resizable=True, finalize=True)
window['multiline'].expand(expand_x=True, expand_y=True)
window['table'].Widget.pack(fill='y')
window['table'].Widget.master.pack(fill='y', expand=False)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event, values)
window.close()
It will generate event 'WIN_CLOSE".
window = sg.Window("Title", layout, finalize=True)
window.TKroot.protocol("WM_DESTROY_WINDOW", lambda:window.write_event_value("WIN_CLOSE", ()))
window.TKroot.protocol("WM_DELETE_WINDOW", lambda:window.write_event_value("WIN_CLOSE", ()))
import PySimpleGUI as sg
layout = [
[sg.Input('', key='-INPUT1-')],
[sg.Input('', key='-INPUT2-'), sg.Button('Go', bind_return_key=True, visible=False)],
]
window = sg.Window("Title", layout, finalize=True)
window['-INPUT1-'].bind("<Return>", "Return-")
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event, values)
window.close()
-INPUT1-Return- {'-INPUT1-': '', '-INPUT2-': ''} # return key pressed in input 1
Go {'-INPUT1-': '', '-INPUT2-': ''} # return key pressed in input 2
Following code show how to switch between multiple windows.
Note:
from tkinter import ttk
import PySimpleGUI as sg
sg.theme("DarkBlue")
sg.set_options(font=('Courier New', 16))
win_1 = [
[sg.Text('Name'), sg.Input(key='name')],
[sg.Text('Password'), sg.Input(key='pass')],
[sg.Button('Sign In'), sg.Button('Exit')]
]
win_2 = [
[sg.Text('WELCOME', justification='center', size=(25, 2), pad=(25, 25))],
[sg.Text('What do you want to do?')],
[sg.Button('Submit'), sg.Button('Cancel')]
]
tab_group = [
[sg.Tab("TAB 1", win_1, key="WIN 1")],
[sg.Tab("TAB 2", win_2, key="WIN 2")],
]
layout = [
[sg.TabGroup(tab_group, border_width=0, pad=(0, 0), key='TABGROUP')],
]
window = sg.Window("Title", layout, finalize=True)
style = ttk.Style()
style.layout('TNotebook.Tab', []) # Hide tab bar
#window['TABGROUP'].Widget.configure(width=600, height=400) # Set size
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, "Exit"):
break
elif event == "Sign In":
window["WIN 2"].select()
elif event == "Cancel":
window["WIN 1"].select()
window.close()
tk.PhotoImage
not just for accessing of image, no image.
If you set image for sg.Image
by option data
, you can get base64 data back from
image1_base64 = window['-IMAGE1-'].Widget.image.cget('data')
image2_base64 = window['-IMAGE2-'].Widget.image.cget('data')
Convert base64 to PIL Image,
from base64 import b64decode
from io import BytesIO
from PIL import Image
img = b64decode(image1_base64)
buf = BytesIO(img)
im = Image.open(buf)
first option is value
for sg.Text
, sg.Checkbox
, sg.InputText
, sg.Multiline
, and text
for sg.Button
def update(key, value):
element = window[key]
if isinstance(element, (sg.Text, sg.Checkbox, sg.InputText, sg.Multiline)):
window[key].update(value=value)
elif isinstance(element, sg.Button):
window[key].update(text=value)
import PySimpleGUI as sg
def my_popup(window):
layout = [
[sg.Text("Here's the \nuser-defined \npopup !!!")],
[sg.Button("OK"), sg.Button("CANCEL")]
]
win = sg.Window("My Popup", layout, modal=True,
grab_anywhere=True, enable_close_attempted_event=True)
event, value = win.read()
if event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT:
event = "CANCEL"
win.close()
window.write_event_value(event, None)
sg.theme("DarkBlue")
sg.set_options(font=("Courier New", 16))
layout = [
[sg.Text("Click following button to open a user-defined popup")],
[sg.Button("POPUP")],
]
window = sg.Window("Title", layout, finalize=True)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event == "POPUP":
my_popup(window)
elif event in ("OK", "CANCEL"):
print(event, repr(values[event]))
window.close()
No option 'undo'
for element sg.Multiline
now.
import PySimpleGUI as sg
def redo(event, text):
try: # if nothing to redo will cause "_tkinter.TclError: nothing to redo"
text.edit_redo()
except:
pass
layout = [
[sg.Multiline('', size=(50, 10), key='MULTILINE')]
]
window = sg.Window("Title", layout, finalize=True)
text = window['MULTILINE'].Widget
# Enable the undo mechanism
text.configure(undo=True)
# Bind redo mechanism to key Ctrl-Shift-Z
text.bind('<Control-Shift-Key-Z>', lambda event, text=text:redo(event, text))
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
window.close()
Event binding
Unless you specify otherwise, the bindings happen in the following order:
if there is a binding directly on the widget it will be fired before any other bindings.
if there is a binding on the widget's class, it is fired next
if there is a binding on the toplevel widget that contains the widget, it is fired next (note: the root window is considered a toplevel window in this context)
if there is a binding on "all" it will fire next.
There should be internal binding for 'grab' on some widgets, like scrollbar,.....
They generate events from '<B1-Motion>'
, '<ButtonRelease-1>'
, then '<ButtonPress-1>'
. So add one variable to confirm '<ButtonPress-1>'
should go first, else variable will be None and no grab action.
import tkinter as tk
from tkinter import ttk
import PySimpleGUI as sg
class Window(sg.Window):
NO_DRAG_WIDGETS = (tk.Button, tk.Entry, tk.Scrollbar, tk.Listbox, tk.Message,
tk.PanedWindow, tk.Scale, tk.Text, ttk.Entry, ttk.PanedWindow, ttk.Scale,
ttk.Scrollbar, ttk.Sizegrip, ttk.Treeview, type(None))
drag_widget = None
def _StartMove(self, event):
"""
Used by "Grab Anywhere" style windows. This function is bound to mouse-down. It marks the beginning of a drag.
:param event: event information passed in by tkinter. Contains x,y position of mouse
:type event: (event)
"""
try:
self.TKroot.x = event.x
self.TKroot.y = event.y
self.drag_widget = event.widget
except:
pass
def _StopMove(self, event):
"""
Used by "Grab Anywhere" style windows. This function is bound to mouse-up. It marks the ending of a drag.
Sets the position of the window to this final x,y coordinates
:param event: event information passed in by tkinter. Contains x,y position of mouse
:type event: (event)
"""
try:
self.TKroot.x = event.x
self.TKroot.y = event.y
self.drag_widget = None
except Exception as e:
print('stop move error', e, event)
def _OnMotion(self, event):
"""
Used by "Grab Anywhere" style windows. This function is bound to mouse motion. It actually moves the window
:param event: event information passed in by tkinter. Contains x,y position of mouse
:type event: (event)
"""
try:
if isinstance(self.drag_widget, Window.NO_DRAG_WIDGETS):
return
deltax = event.x - self.TKroot.x
deltay = event.y - self.TKroot.y
x = self.TKroot.winfo_x() + deltax
y = self.TKroot.winfo_y() + deltay
self.TKroot.geometry("+%s+%s" % (x, y)) # this is what really moves the window
# print('{},{}'.format(x,y))
if Window._move_all_windows:
for window in Window._active_windows:
x = window.TKroot.winfo_x() + deltax
y = window.TKroot.winfo_y() + deltay
window.TKroot.geometry("+%s+%s" % (x, y)) # this is what really moves the window
except Exception as e:
pass
headings = ['President', 'Date of Birth']
data = [
['Ronald Reagan', 'February 6'],
['Abraham Lincoln', 'February 12'],
['George Washington', 'February 22'],
['Andrew Jackson', 'March 15'],
['Thomas Jefferson', 'April 13'],
['Harry Truman', 'May 8'],
['John F. Kennedy', 'May 29'],
['George H. W. Bush', 'June 12'],
['George W. Bush', 'July 6'],
['John Quincy Adams', 'July 11'],
['Garrett Walker', 'July 18'],
['Bill Clinton', 'August 19'],
['Jimmy Carter', 'October 1'],
['John Adams', 'October 30'],
['Theodore Roosevelt', 'October 27'],
['Frank Underwood', 'November 5'],
['Woodrow Wilson', 'December 28'],
]
layout = [
[sg.Text("President to search:")],
[sg.Input(size=(33, 1), key='-INPUT-'), sg.Button('Search')],
[sg.Table(data, headings=headings, justification='left', key='-TABLE-')],
]
window = Window("Title", layout, grab_anywhere=True, finalize=True)
table = window['-TABLE-']
entry = window['-INPUT-']
entry.bind('<Return>', 'RETURN-')
widget = table.Widget
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
# print(event, values)
if event in ('Search', '-INPUT-RETURN-'):
text = values['-INPUT-'].lower()
if text == '':
continue
row_colors = []
for row, row_data in enumerate(data):
if text in row_data[0].lower():
row_colors.append((row, 'green'))
else:
row_colors.append((row, sg.theme_background_color()))
table.update(row_colors=row_colors)
for iid in widget.get_children():
print(iid, widget.item(iid))
window.close()
import PySimpleGUI as sg
layout = [
[sg.Button('Guardar', key='INPUT_1', image_filename='button.png',
border_width=0, button_color=('black', sg.theme_background_color()))],
]
window = sg.Window("Title", layout, finalize=True)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event, values)
window.close()
No option indicatoron
for checkbox or button used as indicator now, so tkinter core required.
The same for option selectcolor
for background color of selected item, 'anchor' for position of text.
import PySimpleGUI as sg
items = [
"Apple", "Apricots", "Avocado", "Banana", "Blackberries", "Blackcurrant",
"Blueberries", "Breadfruit", "Cantaloupe", "Carambola", "Cherimoya",
"Cherries", "Clementine", "Cranberries", "Date Fruit", "Durian",
"Elderberries", "Feijoa", "Figs", "Gooseberries", "Grapefruit",
"Grapes", "Guava", "Jackfruit", "Java-Plum", "Kiwifruit", "Kumquat",
"Lemon", "Longan", "Loquat", "Lychee", "Mandarin", "Mango", "Mangosteen",
"Mulberries", "Nectarine", "Olives", "Orange", "Papaya", "Peaches", "Pear",
"Persimmon", "Pitaya", "Pineapple", "Pitanga", "Plantain", "Plums",
"Pomegranate", "Prunes", "Pummelo", "Quince", "Raspberries", "Rhubarb",
"Sapodilla", "Soursop", "Strawberries", "Tamarind", "Tangerine",
"Watermelon",
]
sg.theme('DarkBlue')
width = 80
size = (max(map(len, items)), 1)
columns = int(width/size[0])
layout = [[sg.Text("Check below items"), sg.Button("Done")]]
row = []
for i, item in enumerate(items):
row.append(sg.Checkbox(item, size=size, key=f'Item {i}'))
j = i + 1
if j % columns == 0:
layout.append(row)
row = []
if row != []:
layout.append(row)
window = sg.Window("Title", layout, finalize=True)
length = len(items)
for i in range(length):
window[f'Item {i}'].Widget.configure(indicatoron=False, selectcolor='blue', # tkinter code required here
anchor='center')
while True:
event, values = window.read()
#print(event, values)
if event == sg.WINDOW_CLOSED:
break
elif event == 'Done':
checked = [items[i] for i in range(length) if values[f'Item {i}']]
print(checked)
window.close()
['Apple', 'Breadfruit', 'Cantaloupe', 'Kumquat', 'Lemon']
import math
import random
import PySimpleGUI as sg
class Gauge(sg.Graph):
"""
Guage same as sg.Graph,
but with three extra arguments, radius, value and gap
gap for internal padding
1. Set gauge = windwo[key], where key is '-Gauge-' defined by yourself.
2. Call gauge.new() after window finalize to get clock drawn.
3. Call gauge.change(value=0, step=1) to set initial values
value as target, step for rotate step, lower step means slow
4. Call guage.change() to rotate pointer by one step
return True if pointer already arrive value set in step 3.
5. Call guage.goto(value) to rotate pointer to value directly.
6. value not defined in then range 0 to 100, type is float or int.
"""
def __new__(cls, radius=100, value=0, gap=5, **kwargs):
"""
To define Guage same as sg.Graph
"""
return sg.Graph.__new__(cls)
def __init__(self, radius=100, value=0, gap=5, **kwargs):
"""
canvas_size, graph_bottom_left, graph_top_right in sg.Graph
supplied automatically here.
:param radius: radius of Guage
:type radius: int
:param value: value of pointer, from 0 to 100
:type value: int | float
:param gap: internal padding of Graph
:type gap: int
:param kwargs: refer sg.Graph, arguments for canvas_size, graph_bottom_left, graph_top_right are not necessary.
:type kwargs: dict
"""
self.radius = radius
self.value = value
self.pointer_stop = 0
self.pointer_step = 1
self.start, self.stop = 0, 100
self.minor_tick_step = 5
self.major_tick_step = 10
self.figure = []
self.figure_pointer = []
self.gap = gap
self.element_color()
self.element_radius()
self.element_width()
super().__init__((2*radius+2*gap, int(1.2*radius+2*gap)),
(-radius-gap, int(-0.2*radius-gap)), (radius+gap, radius+gap),
**kwargs)
def new(self):
"""
Draw Gauge
"""
self.new_clock()
self.new_minor_tick()
self.new_major_tick()
self.new_pointer()
def element_color(self):
"""
Colors of all elements in Guage defined here,
you can update them directly
"""
self.clock_color = None
self.clock_line_color = sg.theme_text_color()
self.minor_tick_color = sg.theme_input_background_color()
self.major_tick_color = sg.theme_text_color()
self.pointer_outer_color = sg.theme_text_color()
self.pointer_color = sg.theme_text_color()
self.origin_outer_color = sg.theme_text_color()
self.origin__color = sg.theme_text_color()
def element_radius(self):
"""
Size of all elements in Guage defined here,
you can update them directly
"""
self.clock_radius = self.radius
self.minor_tick_radius = 0.9*self.radius
self.major_tick_radius = 0.8*self.radius
self.pointer_inner_radius = 0.1*self.radius
self.pointer_outer_radius = 0.7*self.radius
def element_width(self):
"""
Line width of all elements in Guage defined here,
you can update them directly
"""
self.clock_width = 1
self.minor_tick_width = 1
self.major_tick_width = 2
self.pointer_width = 1
self.origin_width = 2
def new_clock(self):
"""
Draw clock
"""
self.figure.append(self.draw_arc((-self.radius, +self.radius),
(+self.radius, -self.radius), 180, 0, style='arc',
arc_color=self.clock_line_color, fill_color=self.clock_color,
line_width=self.clock_width))
def new_minor_tick(self):
"""
Draw minor tick
"""
self.new_tick(self.minor_tick_radius, self.minor_tick_color,
self.minor_tick_width, self.minor_tick_step)
def new_major_tick(self):
"""
Draw major tick
"""
self.new_tick(self.major_tick_radius, self.major_tick_color,
self.major_tick_width, self.major_tick_step)
def new_tick(self, inner_radius, color, width, step):
"""
Draw tick
:param inner_radius: distance from center to tick
:type inner_radius: int
:param color: line color of tick
:type color: tkinter color representation
:param width: line width of tick
:type width: int
:param step: how many differences in value for one tick, must > 0
:type step: int
"""
for i in range(0, 101, step):
j = (100 - i)/100*math.pi
start_x = inner_radius*math.cos(j)
start_y = inner_radius*math.sin(j)
stop_x = self.radius*math.cos(j)
stop_y = self.radius*math.sin(j)
self.figure.append(self.draw_line((start_x, start_y),
(stop_x, stop_y), color=color, width=width))
def new_pointer(self):
"""
Draw pointer
"""
if self.figure_pointer:
for figure in self.figure_pointer:
self.DeleteFigure(figure)
self.figure_pointer = []
d = self.value*1.8 - 90
dx1 = int(2*self.pointer_inner_radius*math.sin(d/180*math.pi))
dy1 = int(2*self.pointer_inner_radius*math.cos(d/180*math.pi))
dx2 = int(self.pointer_outer_radius*math.sin(d/180*math.pi))
dy2 = int(self.pointer_outer_radius*math.cos(d/180*math.pi))
self.figure_pointer.append(self.draw_line(
(-dx1, -dy1), (+dx2, +dy2), color=self.pointer_color,
width=self.pointer_width))
self.figure_pointer.append(self.draw_circle(
(0, 0), self.pointer_inner_radius, fill_color=self.origin__color,
line_color=self.origin_outer_color, line_width=self.origin_width))
def change(self, value=None, step=1):
"""
Rotation of pointer
call it with degree and step to set initial options for rotation.
Without any option to start rotation.
:param value: value of pointer, from 0 to 100
:type value: int | float
:param step: step for rotate step, lower step means slow, must > 0
:type step: int
"""
if value != None:
self.pointer_stop = value
self.pointer_step = step if self.value <= value else -step
return True
now = self.value
step = self.pointer_step
new_value = now + step
if ((step > 0 and new_value < self.pointer_stop) or
(step < 0 and new_value > self.pointer_stop)):
self.value = new_value
self.new_pointer()
return False
else:
self.value = self.pointer_stop
self.new_pointer()
return True
def goto(self, value=0):
"""
Rotate pointer to value directly
:param value: value of pointer, from 0 to 100
:type value: int | float
"""
self.change(value=value, step=abs(self.value-value))
self.change()
def column(row, col):
layout_column = [
[Gauge(radius=50, gap=2, pad=(0, 0), key=f'Gauge ({row},{col})')],
[sg.Text(size=(10, 1), pad=(3, 0), justification='center', key=f'Value ({row},{col})')],
]
return sg.Column(layout_column, pad=(0, 0))
sg.theme('DarkPurple6')
sg.set_options(font=('Courier New', 14))
rows, cols = 3, 5
layout = [
[sg.Button('Reset', size=(6, 1), key='Reset')]] + [
[column(row, col) for col in range(cols)] for row in range(rows)
]
window = sg.Window('Meter', layout=layout, finalize=True,
element_justification='c')
for row in range(rows):
for col in range(cols):
window[f'Gauge ({row},{col})'].new()
window[f'Gauge ({row},{col})'].change(value=0, step=2)
while True:
event, values = window.read(timeout=100)
if event in ('Quit', sg.WIN_CLOSED):
break
if event == 'Reset':
new_value = 0
for row in range(rows):
for col in range(cols):
window[f'Gauge ({row},{col})'].goto(new_value)
for row in range(rows):
for col in range(cols):
if window[f'Gauge ({row},{col})'].change(): # check if target arrived
new_value = random.randint(0, 1000)/10
window[f'Value ({row},{col})'].update(f'{new_value}%')
window[f'Gauge ({row},{col})'].change(value=new_value, step=10)
window.close()
from random import choice
import PySimpleGUI as sg
def all_nodes(item=''):
children = tree.get_children(item)
all_items = list(children).copy()
for child in children:
all_items.extend(all_nodes(child))
return all_items
def expand_all():
for key in all_nodes(''):
tree.item(key, open=True)
treedata = sg.TreeData()
for i in range(10):
parent = choice(list(treedata.tree_dict.keys()))
treedata.insert(parent, i, f'Node {i}', values=[f'Data {i}'])
layout = [
[sg.Tree(data=treedata, headings=['Data', ], auto_size_columns=True,
num_rows=10, col0_width=40, key='-TREE-', show_expanded=False,
enable_events=True),],
[sg.Button('Select All'), sg.Button('Select All Top'),
sg.Button('Select None'), sg.Button('Cancel')]]
window = sg.Window('Tree Element Test', layout, finalize=True)
tree = window['-TREE-'].Widget
expand_all()
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Cancel'):
break
if event == 'Select All Top':
children = tree.get_children('')
tree.selection_set(children)
elif event == 'Select All':
children = all_nodes('')
tree.selection_set(children)
elif event == 'Select None':
tree.selection_set([])
window.close()
from io import BytesIO
from PIL import Image, ImageDraw
import PySimpleGUI as sg
def icon(check):
box = (32, 32)
background = (255, 255, 255, 0)
rectangle = (3, 3, 29, 29)
line = ((9, 17), (15, 23), (23, 9))
im = Image.new('RGBA', box, background)
draw = ImageDraw.Draw(im, 'RGBA')
draw.rectangle(rectangle, outline='black', width=3)
if check == 1:
draw.line(line, fill='black', width=3, joint='curve')
elif check == 2:
draw.line(line, fill='grey', width=3, joint='curve')
with BytesIO() as output:
im.save(output, format="PNG")
png = output.getvalue()
return png
check = [icon(0), icon(1), icon(2)]
headings = ['President', 'Date of Birth', '1', '2', '3']
data = [
['Ronald Reagan', 'February 6'],
['Abraham Lincoln', 'February 12'],
['George Washington', 'February 22'],
['Andrew Jackson', 'March 15'],
['Thomas Jefferson', 'April 13'],
['Harry Truman', 'May 8'],
['John F. Kennedy', 'May 29'],
['George H. W. Bush', 'June 12'],
['George W. Bush', 'July 6'],
['John Quincy Adams', 'July 11'],
['Garrett Walker', 'July 18'],
['Bill Clinton', 'August 19'],
['Jimmy Carter', 'October 1'],
['John Adams', 'October 30'],
['Theodore Roosevelt', 'October 27'],
['Frank Underwood', 'November 5'],
['Woodrow Wilson', 'December 28'],
]
treedata = sg.TreeData()
for president, birthday in data:
treedata.Insert('', president, president, values=[birthday]+[1,2,3],
icon=check[0])
sg.theme('LightPurple')
sg.set_options(font=('Helvetica', 16))
layout = [
[sg.Tree(data=treedata, headings=headings[1:], auto_size_columns=True,
num_rows=10, col0_width=20, key='-TREE-', row_height=48, metadata=[],
show_expanded=False, enable_events=True,
select_mode=sg.TABLE_SELECT_MODE_BROWSE)],
[sg.Button('Quit')]
]
window = sg.Window('Tree as Table', layout, finalize=True)
tree = window['-TREE-']
tree.Widget.heading("#0", text=headings[0]) # Set heading for column #0
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Quit'):
break
elif event == '-TREE-':
president = values['-TREE-'][0]
print(president)
if president in tree.metadata:
tree.metadata.remove(president)
tree.update(key=president, icon=check[0])
else:
tree.metadata.append(president)
tree.update(key=president, icon=check[1])
window.close()
from pathlib import Path
from io import BytesIO
from PIL import Image
import PySimpleGUI as sg
def update_listbox(listbox_element, folder, extension, substring):
path = Path(folder)
filter_ = substring.lower()
lst = []
if folder != '' and path.is_dir():
files = list(path.glob("*.*"))
lst = [file for file in files if file.suffix.lower() in extension
and filter_ in str(file).lower() and file.is_file()]
listbox_element.update(lst)
def update_image(image_element, filename):
im = Image.open(filename)
w, h = size_of_image
scale = max(im.width/w, im.height/h)
if scale <= 1:
image_element.update(filename=filename)
else:
im = im.resize((int(im.width/scale), int(im.height/scale)),
resample=Image.CUBIC)
with BytesIO() as output:
im.save(output, format="PNG")
data = output.getvalue()
image_element.update(data=data)
sg.theme('Dark')
sg.set_options(font=('Courier New', 11))
w, h = size_of_image = (700, 600)
layout_top = [
[sg.InputText(enable_events=True, key='-FOLDER-'),
sg.FolderBrowse('Browse', size=(7, 1), enable_events=True)],
[sg.InputText(enable_events=True, key='-FILTER-'),
sg.Button('Search', size=(7, 1))],
]
layout_bottom = [
[sg.Listbox([], size=(52, 30), enable_events=True,
select_mode=sg.LISTBOX_SELECT_MODE_SINGLE, key='-LISTBOX-')],
]
layout_left = [
[sg.Column(layout_top, pad=(0, 0))],
[sg.Column(layout_bottom, pad=(0, 0))],
]
layout_right = [[sg.Image(background_color='green', key='-IMAGE-')]]
layout = [
[sg.Column(layout_left), sg.Column(layout_right, pad=(0, 0), size=(w+15, h+15), background_color='blue', key='-COLUMN-')],
]
window = sg.Window("PNG/GIF Viewer", layout, finalize=True)
window['-IMAGE-'].Widget.pack(fill='both', expand=True)
window['-IMAGE-'].Widget.master.pack(fill='both', expand=True)
window['-IMAGE-'].Widget.master.master.pack(fill='both', expand=True)
window['-COLUMN-'].Widget.pack_propagate(0)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
# print(event, values)
if event in ('-FOLDER-', '-FILTER-', 'Search'):
update_listbox(window['-LISTBOX-'], values['-FOLDER-'],
('.png', '.gif'), values['-FILTER-'])
elif event == '-LISTBOX-':
lst = values['-LISTBOX-']
if lst != []:
update_image(window['-IMAGE-'], values['-LISTBOX-'][0])
window.close()
For example, using sg.FolderBrowse
here.
The option target
is default as (sg.ThisRow, -1)
, means element just before your sg.FolderBrowse
at same row.
If there's no target in your layout, no event will be generated.
If there's target, event of target will be generated only when target with option enable_events=True
.
So, you should have a target element in your layout, most of time, it is sg.InputText
with option enable_events=True
.
import PySimpleGUI as sg
layout = [
[sg.InputText(enable_events=True), sg.FolderBrowse('Select folder...')],
]
window = sg.Window("Title", layout, finalize=True)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event, values)
if values['Select folder...'] != '':
print(values['Select folder...'])
window.close()
0 {0: 'D:/Document', 'Select folder...': 'D:/Document'}
D:/Document
import PySimpleGUI as sg
layout = [
[sg.InputText(disabled=True, visible=False, enable_events=True), sg.FolderBrowse('Select folder...')],
]
window = sg.Window("Title", layout, finalize=True)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event, values)
if values['Select folder...'] != '':
print(values['Select folder...'])
window.close()
0 {0: 'D:/Document', 'Select folder...': 'D:/Document'}
D:/Document
GUI not update after element update when not window.read
called, can be done by call window.refresh
element.update(option=new_value)
window.refresh()
My case is to simulate a table by using text elements, also with vertical/horizontal scrollbar by slider element.
Mouse moved to any element in this table and wheel up/down without focus here.
Here's my script, but not with few tkinter code for
takefocus=0
of text element to remove focus hightlight dash box.<MouseHweel>
and <Shift-MouseWheel>
to vertical/horizontal scroll callbacks.import PySimpleGUI as sg
def scroll(direction):
for y in range(rows):
for x in range(cols):
if ((x == 0 and y == 0) or (direction == 'V' and y == 0) or
(direction == 'H' and x == 0)):
continue
elif direction == 'V' and x == 0:
window[str((y, 0))].update(data[y+y0-1][0])
elif direction == 'H' and y == 0:
window[str((0, x))].update(data[0][x+x0-1])
else:
window[str((y, x))].update(data[y+y0-1][x+x0-1])
def hscroll(event):
global x0
delta = int(event.delta/120)
x0 = min(max(1, x0-delta), headers-cols+1)
scroll('H')
window['H_Scrollbar'].update(value=x0)
window.refresh()
def vscroll(event):
global y0
delta = int(event.delta/120)
y0 = min(max(1, y0-delta), size-rows+1)
scroll('V')
window['V_Scrollbar'].update(value=size-rows-y0+2)
window.refresh()
def create_data(headers, size):
data = [[f'({row}, {col})' for col in range(headers)]
for row in range(size)]
data[0] = [f'Column {col}' for col in range(headers)]
for row in range(size):
data[row][0] = f'Row {row}'
data[0][0] = 'Features'
return data
headers = 20
size = 50
data = create_data(headers, size)
gap = 2
cols, rows = 5, 15
x0, y0 = 1, 1
sg.theme('Darkgrey')
table = []
for y in range(0, rows):
line = []
for x in range(0, cols):
x_pad = (gap, gap) if x==cols-1 else (gap, 0)
y_pad = (gap, gap) if y==rows-1 else (gap, 0)
pad = (x_pad, y_pad)
bg = 'green' if (x == 0 or y == 0) else 'blue'
line.append(
sg.Text(data[y][x], size=(10, 1), pad=pad, justification='c',
text_color='white', background_color=bg, key=str((y, x)))
)
table.append(line)
option2 = {'resolution':1, 'pad':(0, 0), 'disable_number_display':True,
'enable_events':True}
layout = [
[sg.Column(table, background_color='black', pad=(0, 0), key='Table'),
sg.Slider(range=(1, size-rows+1), size=(18, 20), orientation='v',
**option2, key='V_Scrollbar', expand_y=True)],
[sg.Slider(range=(1, headers-cols+1), size=(46, 20), orientation='h',
**option2, key='H_Scrollbar', expand_x=True)]]
window = sg.Window('Simulate Table', layout, use_default_focus=False,
finalize=True)
for y in range(rows):
for x in range(cols):
element = window[str((y, x))]
element.Widget.configure(takefocus=0)
element.Widget.bind('<MouseWheel>', vscroll)
element.Widget.bind('<Shift-MouseWheel>', hscroll)
element.ParentRowFrame.bind('<MouseWheel>', vscroll)
element.ParentRowFrame.bind('<Shift-MouseWheel>', hscroll)
window['V_Scrollbar'].Update(value=size-rows+1)
window['V_Scrollbar'].Widget.bind('<MouseWheel>', vscroll)
window['H_Scrollbar'].Widget.bind('<Shift-MouseWheel>', hscroll)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event == 'V_Scrollbar':
y0 = size - rows - int(values['V_Scrollbar']) + 2
scroll('V')
elif event == 'H_Scrollbar':
x0 = int(values['H_Scrollbar'])
scroll('H')
window.close()
It is easy for people to make a Menu in PySimpleGUI.
menu_def = [['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
['&Edit', [['Special', 'Normal',['Normal1', 'Normal2'] ], 'Undo'], ],
['&Toolbar', ['---', 'Command &1::Command_Key', 'Command &2', '---', 'Command &3', 'Command &4']],
['&Help', ['&About...']], ]
But people complain that they don't understand / have a way to Disable an item.
A function could search this data structure and change the value / values.
I don't know what operations to offer other than enable/disable.
Can you write a function to help users?
import ctypes
from pathlib import Path
import PySimpleGUI as sg
class Tab():
tabs = {}
keys = []
now = None
def __init__(self, filename=''):
if filename == '':
tab = 'Untitled'
text = ''
key = '0'
else:
tab = Path(filename).name
try:
text = open(file, 'rt').read()
except:
text = ''
if '0' in Tab.keys:
del Tab.tabs['0']
Tab.keys.remove('0')
key = self.get_new_key()
Tab.keys.append(key)
Tab.tabs[key] = {'tab':tab, 'text':text}
Tab.now = key
def layout_frame(self, key):
tab = Tab.tabs[key]['tab']
layout_column = [
[sg.Text(tab, enable_events=True, key=f'File {key}'),
sg.Button("X", pad=(0, 0), border_width=0, key=f'Close {key}',
button_color=("red", sg.theme_background_color()),
font=('Arial', 9, 'bold'))],
]
return [[sg.Column(layout_column, pad=(0, 0), key=f'Column {key}')]]
def layout(self):
menu_def = [['&File', ['&Open', 'E&xit']]]
text = Tab.tabs[Tab.now]['text'] if Tab.now else ''
option = {'default_text':text, 'key':'Multiline', 'size':(130, 35)}
return [
[sg.Menu(menu_def),],
[sg.Frame("", self.layout_frame(key), pad=(0, 0)) for key in Tab.keys],
[sg.Multiline(**option),],
]
def get_new_key(self):
i = 1
while str(i) in Tab.tabs:
i += 1
return str(i)
def tab_color(window, key, color):
window[f'Close {key}'].update(button_color=('white', color))
window[f'File {key}'].update(background_color=color)
def new_window(window):
win = sg.Window("Title", tab.layout(), use_default_focus=False,
size=(1600, 800), element_padding=(0, 0), finalize=True)
for key in Tab.keys:
win[f'Close {key}'].Widget.configure(takefocus=0)
if window:
window.close()
if Tab.now:
Tab.tab_color(win, Tab.now, 'blue')
win.force_focus()
return win
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
sg.theme("DarkBlue")
sg.set_options(font=('Courier New', 12))
tab = Tab()
window = new_window(None)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event == 'Open':
file = sg.popup_get_file("", no_window=True, keep_on_top=True)
if Path(file).is_file():
Tab(file)
window = new_window(window)
elif event.startswith("Close"):
key = event.split(" ")[1]
Tab.keys.remove(key)
del Tab.tabs[key]
Tab.now = Tab.keys[-1] if Tab.keys else None
window = new_window(window)
if Tab.now:
Tab.tab_color(window, Tab.now, 'blue')
elif event.startswith("File"):
key = event.split(" ")[1]
Tab.tab_color(window, Tab.now, sg.theme_background_color())
Tab.now = key
Tab.tab_color(window, Tab.now, 'blue')
window["Multiline"].update(value=Tab.tabs[key]['text'])
print(event)
window.close()
I modified your Gauge program.
I now can collapse the Class Gauge in PyCharm so that it's 1 line of code that I copy.
Adding it to my PySimpleGUI code is easy.
USING the gauge I struggle with. I don't understand the order of the calls to change, etc.
This program makes this GUI:
It is in the Demo Programs folder.
Can you maybe add the parms like I did and make the interface a little different / less confusing?
import PySimpleGUI as sg
import psutil
import sys
import math
"""
Another simple Desktop Widget using PySimpleGUI
This time a CPU Usage indicator using a custom Gauge element
The Gauge class was developed by the brilliant PySimpleGUI user and support-helper @jason990420
It has been hacked on a bit, had classes and functions moved around. It could be cleaned up some
but it's "good enough" at this point to release as a demo.
This is a good example of how you can use Graph Elements to create your own custom elements.
This Gauge element is created from a Graph element.
Copyright 2020 PySimpleGUI.org
"""
class Gauge():
def mapping(func, sequence, *argc):
"""
Map function with extra argument, not for tuple.
: Parameters
func - function to call.
sequence - list for iteration.
argc - more arguments for func.
: Return
list of func(element of sequence, *argc)
"""
if isinstance(sequence, list):
return list(map(lambda i: func(i, *argc), sequence))
else:
return func(sequence, *argc)
def add(number1, number2):
"""
Add two number
: Parameter
number1 - number to add.
numeer2 - number to add.
: Return
Addition result for number1 and number2.
"""
return number1 + number1
def limit(number):
"""
Limit angle in range 0 ~ 360
: Parameter
number: angle degree.
: Return
angel degree in 0 ~ 360, return 0 if number < 0, 360 if number > 360.
"""
return max(min(360, number), 0)
class Clock():
"""
Draw background circle or arc
All angles defined as clockwise from negative x-axis.
"""
def __init__(self, center_x=0, center_y=0, radius=100, start_angle=0,
stop_angle=360, fill_color='white', line_color='black', line_width=2, graph_elem=None):
instance = Gauge.mapping(isinstance, [center_x, center_y, radius, start_angle,
stop_angle, line_width], (int, float)) + Gauge.mapping(isinstance,
[fill_color, line_color], str)
if False in instance:
raise ValueError
start_angle, stop_angle = Gauge.limit(start_angle), Gauge.limit(stop_angle)
self.all = [center_x, center_y, radius, start_angle, stop_angle,
fill_color, line_color, line_width]
self.figure = []
self.graph_elem = graph_elem
self.new()
def new(self):
"""
Draw Arc or circle
"""
x, y, r, start, stop, fill, line, width = self.all
start, stop = (180 - start, 180 - stop) if stop < start else (180 - stop, 180 - start)
if start == stop % 360:
self.figure.append(self.graph_elem.DrawCircle((x, y), r, fill_color=fill,
line_color=line, line_width=width))
else:
self.figure.append(self.graph_elem.DrawArc((x - r, y + r), (x + r, y - r), stop - start,
start, style='arc', arc_color=fill))
def move(self, delta_x, delta_y):
"""
Move circle or arc in clock by delta x, delta y
"""
if False in Gauge.mapping(isinstance, [delta_x, delta_y], (int, float)):
raise ValueError
self.all[0] += delta_x
self.all[1] += delta_y
for figure in self.figure:
self.graph_elem.MoveFigure(figure, delta_x, delta_y)
class Pointer():
"""
Draw pointer of clock
All angles defined as clockwise from negative x-axis.
"""
def __init__(self, center_x=0, center_y=0, angle=0, inner_radius=20,
outer_radius=80, outer_color='white', pointer_color='blue',
origin_color='black', line_width=2, graph_elem=None):
instance = Gauge.mapping(isinstance, [center_x, center_y, angle, inner_radius,
outer_radius, line_width], (int, float)) + Gauge.mapping(isinstance,
[outer_color, pointer_color, origin_color], str)
if False in instance:
raise ValueError
self.all = [center_x, center_y, angle, inner_radius, outer_radius,
outer_color, pointer_color, origin_color, line_width]
self.figure = []
self.stop_angle = angle
self.graph_elem = graph_elem
self.new(degree=angle)
def new(self, degree=0):
"""
Draw new pointer by angle, erase old pointer if exist
degree defined as clockwise from negative x-axis.
"""
(center_x, center_y, angle, inner_radius, outer_radius,
outer_color, pointer_color, origin_color, line_width) = self.all
if self.figure != []:
for figure in self.figure:
self.graph_elem.DeleteFigure(figure)
self.figure = []
d = degree - 90
self.all[2] = degree
dx1 = int(2 * inner_radius * math.sin(d / 180 * math.pi))
dy1 = int(2 * inner_radius * math.cos(d / 180 * math.pi))
dx2 = int(outer_radius * math.sin(d / 180 * math.pi))
dy2 = int(outer_radius * math.cos(d / 180 * math.pi))
self.figure.append(self.graph_elem.DrawLine((center_x - dx1, center_y - dy1),
(center_x + dx2, center_y + dy2),
color=pointer_color, width=line_width))
self.figure.append(self.graph_elem.DrawCircle((center_x, center_y), inner_radius,
fill_color=origin_color, line_color=outer_color, line_width=line_width))
def move(self, delta_x, delta_y):
"""
Move pointer with delta x and delta y
"""
if False in Gauge.mapping(isinstance, [delta_x, delta_y], (int, float)):
raise ValueError
self.all[:2] = [self.all[0] + delta_x, self.all[1] + delta_y]
for figure in self.figure:
self.graph_elem.MoveFigure(figure, delta_x, delta_y)
class Tick():
"""
Create tick on click for minor tick, also for major tick
All angles defined as clockwise from negative x-axis.
"""
def __init__(self, center_x=0, center_y=0, start_radius=90, stop_radius=100,
start_angle=0, stop_angle=360, step=6, line_color='black', line_width=2, graph_elem=None):
instance = Gauge.mapping(isinstance, [center_x, center_y, start_radius,
stop_radius, start_angle, stop_angle, step, line_width],
(int, float)) + [Gauge.mapping(isinstance, line_color, (list, str))]
if False in instance:
raise ValueError
start_angle, stop_angle = Gauge.limit(start_angle), Gauge.limit(stop_angle)
self.all = [center_x, center_y, start_radius, stop_radius,
start_angle, stop_angle, step, line_color, line_width]
self.figure = []
self.graph_elem = graph_elem
self.new()
def new(self):
"""
Draw ticks on clock
"""
(x, y, start_radius, stop_radius, start_angle, stop_angle, step,
line_color, line_width) = self.all
start_angle, stop_angle = (180 - start_angle, 180 - stop_angle
) if stop_angle < start_angle else (180 - stop_angle, 180 - start_angle)
for i in range(start_angle, stop_angle + 1, step):
start_x = x + start_radius * math.cos(i / 180 * math.pi)
start_y = y + start_radius * math.sin(i / 180 * math.pi)
stop_x = x + stop_radius * math.cos(i / 180 * math.pi)
stop_y = y + stop_radius * math.sin(i / 180 * math.pi)
self.figure.append(self.graph_elem.DrawLine((start_x, start_y),
(stop_x, stop_y), color=line_color, width=line_width))
def move(self, delta_x, delta_y):
"""
Move ticks by delta x and delta y
"""
if False in Gauge.mapping(isinstance, [delta_x, delta_y], (int, float)):
raise ValueError
self.all[0] += delta_x
self.all[1] += delta_y
for figure in self.figure:
self.graph_elem.MoveFigure(figure, delta_x, delta_y)
"""
Create Gauge
All angles defined as count clockwise from negative x-axis.
Should create instance of clock, pointer, minor tick and major tick first.
"""
def __init__(self, center=(0, 0), start_angle=0, stop_angle=180, major_tick_width=5, minor_tick_width=2,major_tick_start_radius=90, major_tick_stop_radius=100, major_tick_step=30, clock_radius=100, pointer_line_width=5, pointer_inner_radius=10, pointer_outer_radius=75, pointer_color='white', pointer_origin_color='black', pointer_outer_color='white', pointer_angle=0, degree=0, clock_color='white', major_tick_color='black', minor_tick_color='black', minor_tick_start_radius=90, minor_tick_stop_radius=100, graph_elem=None):
self.clock = Gauge.Clock(start_angle=start_angle, stop_angle=stop_angle, fill_color=clock_color, radius=clock_radius, graph_elem=graph_elem)
self.minor_tick = Gauge.Tick(start_angle=start_angle, stop_angle=stop_angle, line_width=minor_tick_width, line_color=minor_tick_color, start_radius=minor_tick_start_radius, stop_radius=minor_tick_stop_radius, graph_elem=graph_elem)
self.major_tick = Gauge.Tick(start_angle=start_angle, stop_angle=stop_angle, line_width=major_tick_width, start_radius=major_tick_start_radius, stop_radius=major_tick_stop_radius, step=major_tick_step, line_color=major_tick_color, graph_elem=graph_elem)
self.pointer = Gauge.Pointer(angle=pointer_angle, inner_radius=pointer_inner_radius, outer_radius=pointer_outer_radius, pointer_color=pointer_color, outer_color=pointer_outer_color, origin_color=pointer_origin_color, line_width=pointer_line_width, graph_elem=graph_elem)
self.center_x, self.center_y = self.center = center
self.degree = degree
self.dx = self.dy = 1
def move(self, delta_x, delta_y):
"""
Move gauge to move all componenets in gauge.
"""
self.center_x, self.center_y =self.center = (
self.center_x+delta_x, self.center_y+delta_y)
if self.clock:
self.clock.move(delta_x, delta_y)
if self.minor_tick:
self.minor_tick.move(delta_x, delta_y)
if self.major_tick:
self.major_tick.move(delta_x, delta_y)
if self.pointer:
self.pointer.move(delta_x, delta_y)
def change(self, degree=None, step=1):
"""
Rotation of pointer
call it with degree and step to set initial options for rotation.
Without any option to start rotation.
"""
if self.pointer:
if degree != None:
self.pointer.stop_degree = degree
self.pointer.step = step if self.pointer.all[2] < degree else -step
return True
now = self.pointer.all[2]
step = self.pointer.step
new_degree = now + step
if ((step > 0 and new_degree < self.pointer.stop_degree) or
(step < 0 and new_degree > self.pointer.stop_degree)):
self.pointer.new(degree=new_degree)
return False
else:
self.pointer.new(degree=self.pointer.stop_degree)
return True
ALPHA = 0.8
THEME = 'Dark purple 6 '
UPDATE_FREQUENCY_MILLISECONDS = 2 * 1000
def human_size(bytes, units=(' bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
""" Returns a human readable string reprentation of bytes"""
return str(bytes) + ' ' + units[0] if bytes < 1024 else human_size(bytes >> 10, units[1:])
def main(location):
sg.theme(THEME)
gsize = (100, 55)
layout = [
[sg.T('CPU', font='Any 20', background_color='black')],
[sg.Graph(gsize, (-gsize[0] // 2, 0), (gsize[0] // 2, gsize[1]), key='-Graph-')],
[sg.T(size=(5, 1), font='Any 20', justification='c', background_color='black', k='-gauge VALUE-')]]
window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, background_color='black', element_justification='c', finalize=True, right_click_menu=[[''], 'Exit'])
gauge = Gauge(pointer_color=sg.theme_text_color(), clock_color=sg.theme_text_color(), major_tick_color=sg.theme_text_color(),
minor_tick_color=sg.theme_input_background_color(), pointer_outer_color=sg.theme_text_color(), major_tick_start_radius=45,
minor_tick_start_radius=45, minor_tick_stop_radius=50, major_tick_stop_radius=50, major_tick_step=30, clock_radius=50, pointer_line_width=3, pointer_inner_radius=10, pointer_outer_radius=50, graph_elem=window['-Graph-'])
gauge.change(degree=0)
while True: # Event Loop
cpu_percent = psutil.cpu_percent(interval=1)
if gauge.change():
new_angle = cpu_percent*180/100
window['-gauge VALUE-'].update(f'{int(cpu_percent)}%')
gauge.change(degree=new_angle, step=180)
gauge.change()
# ----------- update the graphics and text in the window ------------
# update the window, wait for a while, then check for exit
event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS)
if event == sg.WIN_CLOSED or event == 'Exit':
break
window.close()
if __name__ == '__main__':
if len(sys.argv) > 1:
location = sys.argv[1].split(',')
location = (int(location[0]), int(location[1]))
else:
location = (None, None)
main(location)
from copy import deepcopy
import PySimpleGUI as sg
class Menu(sg.Menu):
def __new__(cls, menu_def, **kwargs):
"""
To define Guage same as sg.Graph
"""
return sg.Menu.__new__(cls)
def __init__(self, menu_def, **kwargs):
self.def_ = deepcopy(menu_def)
super().__init__(menu_def, **kwargs)
def replace(self, menu_def, old, new):
for i, item in enumerate(menu_def):
if isinstance(item, str):
if item == old:
menu_def[i] = new
return True
else:
if self.replace(item, old, new):
return True
return False
def enable(self, item):
if item[0] == "!" and self.replace(self.def_, item, item[1:]):
self.update(menu_definition=deepcopy(self.def_))
def disable(self, item):
if item[0] != "!" and self.replace(self.def_, item, "!"+item):
self.update(menu_definition=deepcopy(self.def_))
menu_def = [['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
['&Edit', [['Special', 'Normal',['Normal1', 'Normal2'] ], 'Undo'], ],
['&Toolbar', ['---', 'Command &1::Command_Key', 'Command &2', '---', 'Command &3', 'Command &4']],
['&Help', ['&About...']], ]
sg.theme("DarkBlue")
sg.set_options(font=('Courier New', 16))
menu_def = [['&File', ['&Open Ctrl-O', '&Save Ctrl-S', '&Properties', 'E&xit']],
['&Edit', [['Special', 'Normal',['Normal1', 'Normal2'] ], 'Undo'], ],
['&Toolbar', ['---', 'Command &1::Command_Key', 'Command &2', '---', 'Command &3', 'Command &4']],
['&Help', ['&About...']], ]
item_list = ['&File', '&Open Ctrl-O', '&Save Ctrl-S', '&Properties',
'E&xit', '&Edit', 'Special', 'Normal', 'Normal1', 'Normal2', 'Undo',
'&Toolbar', 'Command &1::Command_Key', 'Command &2', 'Command &3',
'Command &4', '&Help', '&About...']
layout = [
[Menu(menu_def, key='MENU')],
[sg.Button('Enable'), sg.Button('Disable')],
[sg.Combo(item_list, default_value='&File', size=(25, 1), readonly=True,
key='Combo')],
]
window = sg.Window("Title", layout, finalize=True)
menu = window['MENU']
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
item = values['Combo']
if event == 'Enable':
item = item if item[0] == '!' else '!'+item
menu.enable(item)
elif event == 'Disable':
item = item if item[0] != '!' else item[1:]
menu.disable(item)
window.close()
from datetime import datetime
import PySimpleGUI as sg
def popup_get_time(start_hour=None, start_minute=None):
"""
Using mouse wheel to select hour and minute
:param start_hour: Default value for hour. 0 ~ 23
:type start_hour: int
:param start_minute: Default value for hour. 0 ~ 59
:type start_minute: int
"""
option = {
"font": ("Courier New", 48, "bold"),
"enable_events": True,
"background_color": None,
"text_color": "white",
"justification": "center",
"pad": (0, 0)
}
now = datetime.now()
hour, minute = now.hour, now.minute
hour = hour if start_hour is None else max(0, min(int(start_hour), 23))
minute = minute if start_minute is None else max(0, min(int(start_minute), 59))
layout = [
[sg.Text(f"{hour:0>2d}", **option, size=(3, 1), key="Hour"),
sg.Text(":", **option),
sg.Text(f"{minute:0>2d}", **option, size=(3, 1), key="Minute")],
[sg.Button("OK"), sg.Button("Cancel")],
]
window = sg.Window("Select Time", layout, grab_anywhere=True,
keep_on_top=True, modal=True, finalize=True)
hour_element, minute_element = window['Hour'], window['Minute']
hour_element.bind("<MouseWheel>", "_Wheel")
minute_element.bind("<MouseWheel>", "_Wheel")
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, "Cancel"):
window.close()
return (None, None)
elif event == "OK":
window.close()
return (hour, minute)
elif event == "Hour_Wheel":
delta = -int(hour_element.user_bind_event.delta/120)
hour = (hour+delta) % 24
hour_element.update(f'{hour:0>2d}')
elif event == "Minute_Wheel":
delta = -int(minute_element.user_bind_event.delta/120)
minute = (minute+delta) % 60
minute_element.update(f'{minute:0>2d}')
print(popup_get_time(5, 20))
Next one for user element
import PySimpleGUI as sg
class Time(sg.Column):
elements = []
def __init__(self, hour=0, minute=0, second=0, **options):
self.hour_value = hour
self.minute_value = minute
self.second_value = second
self.hour = sg.Text(f"{hour:0>2d}" , **options)
self.minute = sg.Text(f"{minute:0>2d}", **options)
self.second = sg.Text(f"{minute:0>2d}", **options)
layout = [[self.hour, sg.Text(":", **options), self.minute, sg.Text(":", **options), self.second]]
super().__init__(layout)
Time.elements.append(self)
@classmethod
def initial(self):
for element in Time.elements:
element.hour .Widget.bind("<MouseWheel>", element.callback)
element.minute.Widget.bind("<MouseWheel>", element.callback)
element.second.Widget.bind("<MouseWheel>", element.callback)
def callback(self, event):
widget = event.widget
delta = int(event.delta/120)
if widget == self.hour.Widget:
self.hour_value = (self.hour_value+delta) % 24
self.hour.update(f'{self.hour_value:0>2d}')
elif widget == self.minute.Widget:
self.minute_value = (self.minute_value+delta) % 60
self.minute.update(f'{self.minute_value:0>2d}')
else:
self.second_value = (self.second_value+delta) % 60
self.second.update(f'{self.second_value:0>2d}')
def get(self):
return f'{self.hour_value:0>2d}:{self.minute_value:0>2d}:{self.second_value:0>2d}'
font0 = ("Courier New", 11)
font1 = ("Courier New", 48)
sg.theme("DarkBlue3")
sg.set_options(font=font0)
time1 = Time(font=font1, pad=(0, 0), background_color='white', text_color='blue')
time2 = Time(font=font1, pad=(0, 0), background_color='blue', text_color='white')
layout = [[time1], [time2], [sg.Push(), sg.Button("Time")]]
window = sg.Window("test", layout, finalize=True)
Time.initial()
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Exit'):
break
elif event == 'Time':
print(time1.get(), time2.get())
window.close()
from random import choice
import PySimpleGUI as sg
treedata = sg.TreeData()
for i in range(10):
parent = choice(list(treedata.tree_dict.keys()))
treedata.insert(parent, i, f'Node {i}', values=[f'Data {i}'])
layout = [
[sg.Tree(data=treedata, headings=['Data', ], auto_size_columns=True,
num_rows=10, col0_width=40, key='-TREE-', show_expanded=False,
enable_events=True),],
]
window = sg.Window('Tree Element Test', layout, finalize=True)
tree = window['-TREE-'].Widget
window['-TREE-'].bind("<<TreeviewOpen>>", "EXPAND-")
window['-TREE-'].bind("<<TreeviewClose>>", "COLLAPSE-")
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Cancel'):
break
elif event == '-TREE-EXPAND-':
item = tree.item(tree.focus())
print(f"Expand at item {item}")
elif event == '-TREE-COLLAPSE-':
item = tree.item(tree.focus())
print(f"Collapse at item {item}")
window.close()
Expand at item {'text': 'Node 0', 'image': '', 'values': ['Data 0'], 'open': True, 'tags': ''}
Collapse at item {'text': 'Node 0', 'image': '', 'values': ['Data 0'], 'open': False, 'tags': ''}
Expand at item {'text': 'Node 2', 'image': '', 'values': ['Data 2'], 'open': True, 'tags': ''}
Expand at item {'text': 'Node 4', 'image': '', 'values': ['Data 4'], 'open': True, 'tags': ''}
import PySimpleGUI as sg
headings = ['President', 'Date of Birth']
data = [
['Ronald Reagan', 'February 6'],
['Abraham Lincoln', 'February 12'],
['George Washington', 'February 22'],
['Andrew Jackson', 'March 15'],
['Thomas Jefferson', 'April 13'],
['Harry Truman', 'May 8'],
['John F. Kennedy', 'May 29'],
['George H. W. Bush', 'June 12'],
['George W. Bush', 'July 6'],
['John Quincy Adams', 'July 11'],
['Garrett Walker', 'July 18'],
['Bill Clinton', 'August 19'],
['Jimmy Carter', 'October 1'],
['John Adams', 'October 30'],
['Theodore Roosevelt', 'October 27'],
['Frank Underwood', 'November 5'],
['Woodrow Wilson', 'December 28'],
]
layout = [
[sg.Text("President to search:")],
[sg.Input(size=(33, 1), key='-INPUT-'), sg.Button('Search')],
[sg.Table(data, headings=headings, justification='left', key='-TABLE-')],
]
window = sg.Window("Title", layout, finalize=True)
table = window['-TABLE-']
entry = window['-INPUT-']
entry.bind('<Return>', 'RETURN-')
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
# print(event, values)
if event in ('Search', '-INPUT-RETURN-'):
text = values['-INPUT-']
if text:
index = None
for i, item in enumerate(data):
if text.lower() in item[0].lower():
index = i
break
if index is not None:
table.set_vscroll_position(index/len(data))
table.update(select_rows=[index])
window.close()
By setting option return_keyboard_events=True
in class sg.Window
, all events generated as seperate events after KeyRelease.
It is impossible to confirm what combination is.
import PySimpleGUI as sg
layout = [
[sg.Text('Hello World !')]
]
window = sg.Window("Title", layout, return_keyboard_events=True, finalize=True)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event, values)
window.close()
After Ctrl+Alt+Shift+C
released, will get following events
C:67
Alt_L:18
Shift_L:16
Control_L:17
So you have to record status of keys, like ALT
, CTRL
, SHIFT
and others, here example for it, but tkinter code required.
Here keyboard auto-repeat function removed, you can check if not special key, like alt, shift and control keys.
import PySimpleGUI as sg
def key_event(event, key):
window.write_event_value(key, event.keysym)
layout = [
[sg.Text('Hello World !')],
[sg.Input()],
]
window = sg.Window("Title", layout, finalize=True)
root = window.TKroot
root.bind('<KeyPress>', lambda event, key='-PRESS-':key_event(event, key))
root.bind('<KeyRelease>', lambda event, key='-RELEASE-':key_event(event, key))
key = {}
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
#print(event, values)
if event == '-PRESS-':
key_sym = values[event]
if key_sym in key:
continue
key[key_sym] = key_sym
print('-'.join([value for key, value in key.items()]))
elif event == '-RELEASE-':
key_sym = values[event]
if key_sym in key:
del key[key_sym]
else:
continue
if key != {}:
print('-'.join([value for key, value in key.items()]))
window.close()
Key pressed by order Alt_L, Control_L, Shift_L, C, then released by reverse order, will get following output.
Alt_L
Alt_L-Control_L
Alt_L-Control_L-Shift_L
Alt_L-Control_L-Shift_L-C
Alt_L-Control_L-Shift_L
Alt_L-Control_L
Alt_L
import PySimpleGUI as sg
sg.theme('DarkBlue')
sg.set_options(font=('Courier New', 12))
cols = 3
rows = 50
rows_show = 10
col_width = 15
data = [[f'Cell ({j:0>2d} ,{i:0>2d})' for i in range(rows)] for j in range(cols)]
all_listbox = [[sg.Listbox(data[i], size=(15, rows), pad=(0, 0),
no_scrollbar=True, enable_events=True, key=f'listbox {i}',
select_mode=sg.LISTBOX_SELECT_MODE_SINGLE) for i in range(cols)]]
layout = [
[sg.Text('Product'.center(col_width), pad=(0, 0)),
sg.Text('Unit Price'.center(col_width), pad=(0, 0)),
sg.Text('Price'.center(col_width), pad=(0, 0))],
[sg.Column(all_listbox, pad=(0, 0), scrollable=True,
vertical_scroll_only=True)],
]
window = sg.Window("Title", layout, finalize=True)
# Align content of list to center & remove underline in listbox
for i in range(cols):
listbox = window[f'listbox {i}'].Widget
listbox.configure(justify='center', activestyle='none')
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
if event.startswith('listbox'):
row = window[event].get_indexes()[0]
for i in range(cols):
window[f'listbox {i}'].update(set_to_index=row)
window.close()
import PySimpleGUI as sg
class Table(sg.Table):
def get_all_iids(self):
"""
Return list of unique id for each row in table, all iid start from '1'
"""
return list(self.Widget.get_children())
def select_to_iid(self, select):
"""
Convert id of selected row into iid
"""
return list(map(lambda item:str(item+1), select))
def delete_iids(self, *iids):
self.Widget.delete(*iids)
def delete(self, select, position):
"""
Delete selected rows in table
select: list of id of selected rows, all PSG id start from 0
"""
if select == []:
self.status.update('No row selected to delete !')
return
if sg.popup_yes_no("Are you sure to delete ?") == 'Yes':
select_iid = self.select_to_iid(select)
self.delete_iids(*select_iid)
self.tree_ids = self.get_all_iids() # update iid list of PSG table
self.update(select_rows=[]) # Deselect rows of table
def move_up(self, select, position):
"""
Move selected rows one step up
select: list of id of selected rows, all PSG id start from 0
"""
if select == []:
self.status.update('No row selected to move up !')
return
iids = self.get_all_iids()
select_iid = self.select_to_iid(select)
index = list(map(iids.index, select_iid)) # move index one step up
if index[0] == 0: # if on the top of table
self.status.update('Selected rows are already on the top of table !')
return
for i in index: # switch with previous one
iids[i], iids[i-1] = iids[i-1], iids[i]
self.Widget.set_children('', *iids) # update iid list of table
self.Widget.see(select_iid[0]) # ensure top one visible
self.tree_ids = iids # update PSG iid table
def move_down(self, select, position):
"""
Move selected rows one step down
select: list of id of selected rows, all PSG id start from 0
"""
if select == []:
self.status.update('No row selected to move down !')
return
iids = self.get_all_iids()
select_iid = self.select_to_iid(select)
index = list(map(iids.index, select_iid))
if index[-1] == len(iids)-1: # if on bottom of table
self.status.update('Selected rows are already on the bottom of table !')
return
for i in reversed(index): # switch with next one
iids[i], iids[i+1] = iids[i+1], iids[i]
self.Widget.set_children('', *iids) # update iid list of table
self.Widget.see(select_iid[-1]) # ensure bottom one visible
self.tree_ids = iids # update PSG iid table
def edit(self, select, position):
"""
Edit data of selected row on another popup window
select: list of id of selected rows, all PSG id start from 0
"""
if len(select) != 1: # Only one row to go edit
self.status.update('Select only one row to edit !')
return
iid = self.select_to_iid(select)[0] # get first iid
values = self.Widget.set(iid) # get values of row iid
size = (max(map(len, self.header)), 1) # size of header text
layout = [
[sg.Text(column, size=size), sg.Input(values[column], key=column)]
for column in self.header] + [[sg.OK(), sg.Cancel()]]
win = sg.Window('Edit', layout, modal=True, finalize=True)
self.remove_dash_box(win)
while True:
evt, vals = win.read()
if evt in (sg.WINDOW_CLOSED, 'Cancel'):
break
elif evt == 'OK':
for column in self.header: # update value of all columns
self.Widget.set(iid, column=column, value=vals[column])
break
win.close()
def get_new_iid(self):
"""
Generate one new unique iid
"""
iids = self.get_all_iids()
i = 1
while str(i) in iids:
i += 1
return str(i)
def insert(self, select, position):
"""
Insert one blank row into table.
select: list of id of selected rows, all PSG id start from 0
position: where to insert, can be 'Before' or 'After' selected rows,
'Top' or 'Bottom' of table.
"""
if position in ('Before', 'After') and select == []:
self.status.update(f'Select one row to insert {position.lower()} !')
return
layout = [
[sg.Text(column, size=(12, 1)), sg.Input('', key=column)]
for column in self.header
] + [[sg.OK(), sg.Cancel()]]
win = sg.Window('Insert', layout, modal=True, finalize=True)
self.remove_dash_box(win)
while True:
evt, vals = win.read()
if evt in (sg.WINDOW_CLOSED, 'Cancel'):
break
elif evt == 'OK':
iids = self.get_all_iids()
select_iid = self.select_to_iid(select)
if position == 'Before': # Find index for insertion
index = iids.index(select_iid[0])
elif position == 'After':
index = iids.index(select_iid[-1])+1
elif position == 'Top':
index = 0
else:
index = len(iids)
# Insert new row with new values into table
values = [vals[column] for column in self.header]
iid = self.get_new_iid()
self.Widget.insert('', index, iid=iid, values=values)
self.Widget.see(iid) # ensure new one visible
self.tree_ids = self.get_all_iids() # update PSG iid table
# new row iid converted into selected id, then new row selected
self.update(select_rows=[int(iid)-1])
break
win.close()
def copy(self, select, position):
"""
Copy values of each selected row to clipboard
select: list of id of selected rows, all PSG id start from 0
"""
if select == []:
self.status.update('No row selected to copy !')
return
select_iid = self.select_to_iid(select)
self.clipboard = []
for iid in select_iid:
data_dict = self.Widget.set(iid) # get row data
self.clipboard.append([data_dict[column] for column in self.header])
def paste(self, select, position):
"""
Paste content of clipboard into table
select: list of id of selected rows, all PSG id start from 0
position: where to paste, can be 'Before' or 'After' selected rows,
'Top' or 'Bottom' of table.
"""
if self.clipboard == None:
self.status.update('No data to paste !')
return
if position in ('Before', 'After') and select == []:
self.status.update(f'Select rows to paste {position.lower()} !')
return
iids = self.get_all_iids()
select_iid = self.select_to_iid(select)
if position == 'Before': # Find index for insertion
index = iids.index(select_iid[0])
elif position == 'After':
index = iids.index(select_iid[-1])+1
elif position == 'Top':
index = 0
else:
index = len(iids)
id_list = []
for i, values in enumerate(self.clipboard): # paste content of clipboard
iid = self.get_new_iid()
id_list.append(int(iid)-1)
self.Widget.insert('', index+i, iid=iid, values=values)
self.tree_ids = self.get_all_iids() # update PSG iid list
self.update(select_rows=id_list) # pasted rows selected
self.Widget.see(str(id_list[-1]+1)) # ensure last row visible
self.Widget.see(str(id_list[0]+1)) # ensure first row visible
def align_headings(self, alignment):
"""
Aligment for headings of table
alignment: list of anchor string for each column of headings
anchor string can be one of
'nw', 'n', 'ne', 'w', 'center', 'e', 'sw', 's', 'se'.
"""
for cid, anchor in enumerate(alignment):
self.Widget.heading(cid, anchor=anchor)
def align_columns(self, alignment):
"""
Aligment for columns of table
alignment: list of anchor string for each column of table
anchor string can be one of
'nw', 'n', 'ne', 'w', 'center', 'e', 'sw', 's', 'se'.
"""
for cid, anchor in enumerate(alignment):
self.Widget.column(cid, anchor=anchor)
def double_click(self, event):
"""
Additional event for double-click on header
event: class event
"""
self.status.update('') # Widget event, so clear status bar here.
# what part clicked
region = self.Widget.identify("region", event.x, event.y)
if region == 'heading': # Only care double-clock on headings
# check which column clicked
cid = int(self.Widget.identify_column(event.x)[1:])-1
values = self.get_all_values() # gete all values and sorting by key
values = sorted(values, key=lambda item:String(item[cid]),
reverse=self.reverse[cid]) # reverse all the time
self.update(values=values) # update full table with sorted values
self.clipboard = None # clear clipboard after sorting
self.reverse[cid] = not self.reverse[cid] # reverse sorting
def initial(self, status, header, header_alignment, column_alignment):
"""
Initial setting for related information
status: statusbar element to show status
header: list of string, headings of table.
header_alignment: list of anchor string for headings
column_alignment: list of anchor string for columns
"""
self.status, self.header = status, header
self.align_headings(header_alignment)
self.align_columns(column_alignment)
# Initialize reverse settings of sorting for each heading
self.reverse = [False]*len(header)
# Add binding of double-click event on headings
self.Widget.bind('<Double-1>', self.double_click, add='+')
def remove_dash_box(self, win):
"""
Remove dash box of element, skip Input-related elements.
"""
for key, element in win.AllKeysDict.items(): # all elements with key
if not isinstance(element,
(sg.InputText, sg.InputCombo, sg.InputOptionMenu)):
element.Widget.configure(takefocus=0) # no keyboard focus
def get_all_values(self):
"""
Get values of all rows in existing table.
"""
return [self.Widget.item(row)['values'] for row in self.get_all_iids()]
class String():
"""
String for comparison in sorting
"""
def __init__(self, value):
self.value = value
try:
self.value = float(value)
self.type = 'number'
except:
self.value = value
self.type = 'string'
def __gt__(self, value):
if self.type == value.type:
result = self.value > value.value
else:
result = False if self.type == 'string' else True
return result
def __ge__(self, value):
if self.type == value.type:
result = self.value >= value.value
else:
result = False if self.type == 'string' else True
return result
def __lt__(self, value):
if self.type == value.type:
result = self.value < value.value
else:
result = True if self.type == 'string' else False
return result
def __le__(self, value):
if self.type == value.type:
result = self.value <= value.value
else:
result = True if self.type == 'string' else False
return result
def __eq__(self, value):
if self.type == value.type:
result = self.value == value.value
else:
result = False
return result
def __repr__(self):
return str(self.value)
# All data should be in string format for no conversion when edit in sg.Input
data = [
["Name", "Cases/All", "Case/Day", "Deaths/All", "Death/Day"],
["Global", "80773033", "563983", "1783619", "11784"],
["USA", "19147627", "174814", "332423", "1779"],
["India", "10244852", "20549", "148439", "286"],
["Brazil", "7504833", "20548", "191570", "431"],
["Russian", "3131550", "26513", "56426", "599"],
["France", "2530400", "11295", "63701", "969"],
["UK", "2382869", "53135", "71567", "458"],
["Italy", "2067487", "11210", "73029", "659"],
["Spain", "1893502", "7717", "50442", "36"],
["Germany", "1687185", "22459", "32107", "1129"],
["Colombia", "1603807", "9310", "42374", "203"],
["Argentina", "1590513", "6586", "42868", "218"],
["Mexico", "1389430", "5996", "122855", "429"],
["Turkey", "1364242", "15805", "20388", "253"],
["Poland", "1281414", "12780", "28019", "565"],
["Iran", "1212481", "6108", "54946", "132"],
["Ukraine", "1045348", "7986", "18324", "243"],
["South Africa", "1021451", "9580", "27568", "497"],
["Peru", "1008908", "1251", "37525", "51"],
["Netherlands", "778293", "7561", "11218", "171"],
]
sg.theme('DarkBlue')
sg.set_options(button_element_size=(6, 1), auto_size_buttons=False,
button_color=('white', 'blue'), font='Courier 11')
frame_button = [[sg.Button('Edit'), sg.Button('Delete'), sg.Button('Insert')]]
frame_copy = [[sg.Button('Copy')], [sg.Button('Paste')]]
frame_move = [[sg.Button('MoveUp')], [sg.Button('MoveDn')]]
frame_radio = [
[sg.Radio('Before', 'Position', size=(8, 1), key='Before'),
sg.Radio('After', 'Position', size=(8, 1), key='After')],
[sg.Radio('Top', 'Position', size=(8, 1), key='Top'),
sg.Radio('Bottom', 'Position', size=(8, 1), key='Bottom', default=True)]]
layout = [
[sg.Column(frame_button), sg.Column(frame_radio), sg.Column(frame_copy),
sg.Column(frame_move)],
[Table(data[1:], headings=data[0], auto_size_columns=False,
def_col_width=13, enable_events=True, key='-TABLE-')],
[sg.StatusBar('Double click header to do sorting', size=(60, 1),
key='-STATUS-')],
]
window = sg.Window('Table', layout, use_default_focus=False, finalize=True)
table, status = window['-TABLE-'], window['-STATUS-']
table.initial(status, data[0], ['center']*5, ['center']+['e']*4)
table.remove_dash_box(window)
function = {
'Edit' :table.edit, 'Delete':table.delete, 'Insert':table.insert,
'Copy' :table.copy, 'Paste' :table.paste, 'MoveUp':table.move_up,
'MoveDn':table.move_down}
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
# print(event, values)
status.update('') # Clear status bar
select = values['-TABLE-']
position = 'Bottom'
for key in ('Before', 'After', 'Top', 'Bottom'):
if values[key]:
position = key
break
if event in function:
function[event](select, position)
window.close()
There's no method to set window keep on top or not in PySimpleGUI.
import PySimpleGUI as sg
def main():
layout = [
[sg.Button('Keep on Top'), sg.Button('Not Keep on Top')]
]
window = sg.Window("Title", layout, keep_on_top=True, finalize=True)
setting = {'Keep on Top':1, 'Not Keep on Top':0}
while True:
event, values = window.read()
print(event, values)
if event == sg.WINDOW_CLOSED:
break
if event in setting:
window.TKroot.wm_attributes("-topmost", setting[event])
window.close()
if __name__ == '__main__':
main()
import ctypes
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont, ImageOps
import PySimpleGUI as sg
def get_data(im):
with BytesIO() as output:
im.save(output, format="PNG")
return output.getvalue()
def blank_image(poker_size, background_color='#f0f0f0ff'):
w, h = poker_size
r = 10
im = Image.new('RGBA', poker_size, (0, 0, 0, 0))
draw = ImageDraw.Draw(im, "RGBA")
option = {'fill':background_color, 'width':0}
draw.rectangle((0, r, w, h-r), **option)
draw.rectangle((r, 0, w-r, h), **option)
draw.ellipse((0, 0, 2*r, 2*r), **option)
draw.ellipse((w-2*r, 0, w, 2*r), **option)
draw.ellipse((0, h-2*r, 2*r, h), **option)
draw.ellipse((w-2*r, h-2*r, w, h), **option)
return im
def text_image(text, color, size):
font = ImageFont.truetype(font='arial.ttf', size=100)
w, h = font.getsize(text)
im = Image.new('RGBA', (w, h), (0, 0, 0, 0))
draw = ImageDraw.Draw(im, "RGBA")
draw.text((0, 0), text, fill=color, font=font, anchor='lt')
im = im.crop(im.getbbox())
if text == '10':
w, h = size
size = (int(w*1.6), h)
im = im.resize(size, resample=Image.CUBIC)
return im
def all_cards(card_size):
card_text = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
card_color = ['black', '#800000ff', '#800000ff', 'black']
card_symbol = ['โ ', 'โฅ', 'โฆ', 'โฃ']
r = 5
size = (12, 18)
blank = blank_image(card_size)
im_text = [[text_image(card_text[i], card_color[j], size)
for i in range(13)] for j in range(4)]
size = (12, 24)
im_symbol = [text_image(card_symbol[i], card_color[i], size)
for i in range(4)]
size = (20, 30)
dx, dy = size[0]//2, size[1]//2
w, h = card_size
hw, hh = w//2, h//2
xg, yg = 25, 15
g1 = (h - 3*size[1]-2*yg)/4
g2 = (h - 4*size[1]-2*yg)/6
x = [xg, hw-dx, w-size[0]-xg]
y = [yg, hh-dy, h-yg-size[1], int(yg+dy+g1), h-yg-size[1]-dy, int(yg+dy+g2),
int(yg+2.5*size[1]+6*g2), int(yg+size[1]+2*g2), int(yg+2*size[1]+4*g2)]
points_list = {
0:[(1, 1)],
1:[(1, 0), (1, 2)],
2:[(1, 0), (1, 1), (1, 2)],
3:[(0, 0), (2, 0), (0, 2), (2, 2)],
4:[(0, 0), (2, 0), (1, 1), (0, 2), (2, 2)],
5:[(0, 0), (2, 0), (0, 1), (2, 1), (0, 2), (2, 2)],
6:[(0, 0), (2, 0), (0, 1), (2, 1), (0, 2), (2, 2), (1, 3)],
7:[(0, 0), (2, 0), (0, 1), (2, 1), (0, 2), (2, 2), (1, 3), (1, 4)],
8:[(0, 0), (0, 7), (0, 8), (0, 2), (2, 0), (2, 7), (2, 8), (2, 2), (1, 1)],
9:[(0, 0), (0, 7), (0, 8), (0, 2), (2, 0), (2, 7), (2, 8), (2, 2), (1, 5), (1, 6)]}
large_symbol = [text_image(card_symbol[i], card_color[i], size)
for i in range(4)]
result = []
for j in range(4):
line = []
for i in range(13):
im = blank.copy()
draw = ImageDraw.Draw(im, "RGBA")
text = im_text[j][i]
d = 5 if i == 9 else 0
im.paste(text, box=(r-d//2, r), mask=text)
im.paste(text, box=(w-r-12-d,r), mask=text)
text = text.transpose(Image.ROTATE_180)
im.paste(text, box=(r-d//2, h-r-18), mask=text)
im.paste(text, box=(w-r-12-d, h-r-18), mask=text)
symbol = im_symbol[j]
im.paste(im_symbol[j], box=(r, r+18+4), mask=symbol)
im.paste(im_symbol[j], box=(w-r-12,r+18+4), mask=symbol)
symbol = symbol.transpose(Image.ROTATE_180)
im.paste(symbol, box=(r, h-r-18*2-10), mask=symbol)
im.paste(symbol, box=(w-r-12, h-r-18*2-10), mask=symbol)
if i == 0:
s = (40, 60) # Very large symbol
offset = 3
box1 = (hw-s[0]//2+offset, hh-s[1]//2+offset)
box2 = (hw-s[0]//2, hh-s[1]//2)
symbol_1 = text_image(card_symbol[j], 'grey', s)
symbol_2 = text_image(card_symbol[j], card_color[j], s)
im.paste(symbol_1, box=box1, mask=symbol_1)
im.paste(symbol_2, box=box2, mask=symbol_2)
elif i < 10:
for point in points_list[i]:
symbol = large_symbol[j]
if point[1] in (2, 4, 6, 8):
symbol = symbol.transpose(Image.ROTATE_180)
pass
im.paste(symbol, box=(x[point[0]], y[point[1]]), mask=symbol)
else:
text = "JACK" if i==10 else "QUEEN" if i==11 else "KING"
symbol = text_image(text, card_color[j], (74, 102))
im.paste(symbol, box=(57-37, 80-51), mask=symbol)
line.append(get_data(im))
result.append(line)
return result
w, h = card_size = (115, 161)
gap = 10
width, height = 13*w+14*gap, 4*h+5*gap
cards = all_cards(card_size)
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
sg.theme('DarkBlue')
layout = [[sg.Graph((width, height), (0, 0), (width, height), key='GRAPH',
background_color='green', pad=(0, 0))]]
window = sg.Window("Title", layout, margins=(0, 0), finalize=True)
top = [[window['GRAPH'].draw_image(data=cards[y][x],
location=(gap+(w+gap)*x, height-(gap+(h+gap)*y)))
for x in range(13)] for y in range(4)]
while True:
event, values = window.read(timeout=1000)
if event == sg.WINDOW_CLOSED:
break
window.close()
from random import randint
import PySimpleGUI as sg
sg.theme('DarkBlue')
layout = [[sg.Text('', size=(50, 1), relief='sunken', font=('Courier', 11),
text_color='yellow', background_color='black',key='TEXT')]]
window = sg.Window('Title', layout, finalize=True)
text = window['TEXT']
state = 0
while True:
event, values = window.read(timeout=100)
if event == sg.WINDOW_CLOSED:
break
state = (state+1)%51
text.update('โ'*state)
window.close()
import ctypes
import PySimpleGUI as sg
def window_dimension(window, monospaced_font):
from tkinter.font import Font
width, height = window.get_screen_dimensions()
tkfont = Font(font=monospaced_font)
w, h = tkfont.measure("A"), tkfont.metrics("linespace")
return width//w, height//h
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
sg.theme("DarkBlue")
font = ('Courier New', 64, 'bold')
sg.set_options(font=font)
layout = [
[sg.Text("", size=(30, 3), justification='center', key='TEXT')],
[sg.Text("", size=(30, 1), justification='center')],
[sg.Text("1234567890"*3, size=(30, 1), pad=(0, 0), justification='center')],
]
window = sg.Window("Title", layout, element_padding=(0, 0), margins=(0, 0),
finalize=True)
text = window['TEXT']
window.maximize()
w, h = window_dimension(window, font)
text.update(value=f'Dimension of window is\n{w}x{h}\nin chars')
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
print(event, values)
window.close()
table = window['-TABLE-'].Widget
# Aligment for Headings
for cid, anchor in enumerate(['w', 'w', 'center', 'e', 'e', 'e', 'e']):
table.heading(cid, anchor=anchor)
import PySimpleGUI as sg
headings = ['President', 'Date of Birth']
data = [
['Ronald Reagan', 'February 6'],
['Abraham Lincoln', 'February 12'],
['George Washington', 'February 22'],
['Andrew Jackson', 'March 15'],
['Thomas Jefferson', 'April 13'],
['Harry Truman', 'May 8'],
['John F. Kennedy', 'May 29'],
['George H. W. Bush', 'June 12'],
['George W. Bush', 'July 6'],
['John Quincy Adams', 'July 11'],
['Garrett Walker', 'July 18'],
['Bill Clinton', 'August 19'],
['Jimmy Carter', 'October 1'],
['John Adams', 'October 30'],
['Theodore Roosevelt', 'October 27'],
['Frank Underwood', 'November 5'],
['Woodrow Wilson', 'December 28'],
]
layout = [
[sg.Text("President to search:")],
[sg.Input(size=(33, 1), key='-INPUT-'), sg.Button('Search')],
[sg.Table(data, headings=headings, justification='left', key='-TABLE-')],
]
window = sg.Window("Title", layout, finalize=True)
table = window['-TABLE-']
entry = window['-INPUT-']
entry.bind('<Return>', 'RETURN-')
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
# print(event, values)
if event in ('Search', '-INPUT-RETURN-'):
text = values['-INPUT-'].lower()
if text == '':
continue
row_colors = []
for row, row_data in enumerate(data):
if text in row_data[0].lower():
row_colors.append((row, 'green'))
else:
row_colors.append((row, sg.theme_background_color()))
table.update(row_colors=row_colors)
window.close()
To get row data (here, iid for each row is type str
)
widget = window['-TABLE-'].Widget
for iid in widget.get_children():
print(iid, widget.item(iid))
1 {'text': '{Ronald Reagan} {February 6}', 'image': '', 'values': ['Ronald Reagan', 'February 6'], 'open': 0, 'tags': [0]}
2 {'text': '{Abraham Lincoln} {February 12}', 'image': '', 'values': ['Abraham Lincoln', 'February 12'], 'open': 0, 'tags': [1]}
3 {'text': '{George Washington} {February 22}', 'image': '', 'values': ['George Washington', 'February 22'], 'open': 0, 'tags': [2]}
4 {'text': '{Andrew Jackson} {March 15}', 'image': '', 'values': ['Andrew Jackson', 'March 15'], 'open': 0, 'tags': [3]}
5 {'text': '{Thomas Jefferson} {April 13}', 'image': '', 'values': ['Thomas Jefferson', 'April 13'], 'open': 0, 'tags': [4]}
6 {'text': '{Harry Truman} {May 8}', 'image': '', 'values': ['Harry Truman', 'May 8'], 'open': 0, 'tags': [5]}
7 {'text': '{John F. Kennedy} {May 29}', 'image': '', 'values': ['John F. Kennedy', 'May 29'], 'open': 0, 'tags': [6]}
8 {'text': '{George H. W. Bush} {June 12}', 'image': '', 'values': ['George H. W. Bush', 'June 12'], 'open': 0, 'tags': [7]}
9 {'text': '{George W. Bush} {July 6}', 'image': '', 'values': ['George W. Bush', 'July 6'], 'open': 0, 'tags': [8]}
10 {'text': '{John Quincy Adams} {July 11}', 'image': '', 'values': ['John Quincy Adams', 'July 11'], 'open': 0, 'tags': [9]}
11 {'text': '{Garrett Walker} {July 18}', 'image': '', 'values': ['Garrett Walker', 'July 18'], 'open': 0, 'tags': [10]}
12 {'text': '{Bill Clinton} {August 19}', 'image': '', 'values': ['Bill Clinton', 'August 19'], 'open': 0, 'tags': [11]}
13 {'text': '{Jimmy Carter} {October 1}', 'image': '', 'values': ['Jimmy Carter', 'October 1'], 'open': 0, 'tags': [12]}
14 {'text': '{John Adams} {October 30}', 'image': '', 'values': ['John Adams', 'October 30'], 'open': 0, 'tags': [13]}
15 {'text': '{Theodore Roosevelt} {October 27}', 'image': '', 'values': ['Theodore Roosevelt', 'October 27'], 'open': 0, 'tags': [14]}
16 {'text': '{Frank Underwood} {November 5}', 'image': '', 'values': ['Frank Underwood', 'November 5'], 'open': 0, 'tags': [15]}
17 {'text': '{Woodrow Wilson} {December 28}', 'image': '', 'values': ['Woodrow Wilson', 'December 28'], 'open': 0, 'tags': [16]}
from random import randint
import PySimpleGUI as sg
class Circular_Progressbar():
def __init__(self, graph_bg='green', bar_radius=50, bar_width=10, gap=5,
bar_color='yellow', progress_color='blue', text_color='white',
text_font=('Courier New', 16)):
self.text_font = text_font
self.text_color = text_color
self.bar_radius = bar_radius
self.bar_width = bar_width
self.bar_color = bar_color
self.graph_bg = graph_bg
self.progress_color = progress_color
self.gap = gap + (self.bar_width+1)//2
self.degree = 0
self.target = 0
self.graph = sg.Graph(
(2*(self.bar_radius+self.gap), 2*(self.bar_radius+self.gap)),
(-self.bar_radius-self.gap, -self.bar_radius-self.gap),
( self.bar_radius+self.gap, self.bar_radius+self.gap),
background_color=self.graph_bg)
self.p, self.t = None, None
def initial(self, angle=0):
self.graph.draw_circle((0, 0), self.bar_radius,
line_color=self.bar_color, line_width=self.bar_width)
angle = min(360, max(0, angle))
self.set_now(0)
self.set_target(0)
def set_target(self, angle=0, step=5):
self.target = min(360, max(0, int(angle)))
self.step = min(360, max(1, int(step)))
def set_now(self, angle=0):
self.angle = min(360, max(0, int(angle)))
def move(self):
if self.target == self.angle:
return True
if self.angle < self.target:
self.angle = min(self.target, self.angle+self.step)
else:
self.angle = max(self.target, self.angle-self.step)
if self.p:
self.graph.delete_figure(self.p)
if self.t:
self.graph.delete_figure(self.t)
text = f'{self.angle/3.6:.1f}%'
r = self.bar_radius
if self.angle == 360:
self.p = self.graph.draw_circle((0, 0), self.bar_radius,
line_color=self.progress_color, line_width=self.bar_width+1)
else:
self.p = self.graph.draw_arc((-r, r), (r, -r), self.angle, 0,
style='arc', arc_color=self.progress_color,
line_width=self.bar_width+1)
self.t = self.graph.draw_text(
text, (0, 0), color=self.text_color, font=self.text_font,
text_location=sg.TEXT_LOCATION_CENTER)
return False
progress_bar = Circular_Progressbar()
layout = [[progress_bar.graph]]
window = sg.Window('Circular Progressbar', layout, finalize=True)
progress_bar.initial()
progress_bar.set_target(randint(0, 360))
while True:
event, values = window.read(timeout=10)
print(event)
if event == sg.WINDOW_CLOSED:
break
elif event == '__TIMEOUT__':
if progress_bar.move():
progress_bar.set_target(randint(0, 360))
window.close()
After content of listbox change, the option size
not changed. After size
changed, call method contents_changed
of element sg.Column
, then the new scroll area is computed to match the new contents.
Another issue for wrong scroll region can be solved by new scroll
function, PySimpleGUI may update in the future.
import ctypes
from random import randint
import PySimpleGUI as sg
def yscroll(self, event): # tkinter code
if self.canvas.yview() == (0.0, 1.0):
return
if event.num == 5 or event.delta < 0:
self.canvas.yview_scroll(1, "unit")
elif event.num == 4 or event.delta > 0:
self.canvas.yview_scroll(-1, "unit")
ctypes.windll.user32.SetProcessDPIAware() # Set unit of GUI to pixels
sg.theme('DarkBlue')
sg.set_options(font=('Courier New', 12))
sg.TkScrollableFrame.yscroll = yscroll # For issue scroll region
cols = 3
rows = randint(5, 20)
rows_show = 10
col_width = 15
data = [[f'Cell ({j:0>2d} ,{i:0>2d})' for i in range(rows)] for j in range(cols)]
all_listbox = [[sg.Listbox(data[i], size=(15, rows), pad=(0, 0),
no_scrollbar=True, enable_events=True, key=f'listbox {i}',
select_mode=sg.LISTBOX_SELECT_MODE_SINGLE) for i in range(cols)]]
layout = [
[sg.Text('Product'.center(col_width), pad=(0, 0)),
sg.Text('Unit Price'.center(col_width), pad=(0, 0)),
sg.Text('Price'.center(col_width), pad=(0, 0))],
[sg.Column(all_listbox, size=(555, 300), pad=(0, 0), scrollable=True,
vertical_scroll_only=True, key='Column')],
[sg.Button('Update'), sg.Text(f"Total rows = {rows}", key='Rows')],
]
window = sg.Window("Title", layout, finalize=True)
for i in range(cols):
listbox = window[f'listbox {i}'].Widget
listbox.configure(justify='center') # tkinter code
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event.startswith('listbox'):
row = window[event].get_indexes()[0]
user_event = False
for i in range(cols):
listbox = window[f'listbox {i}']
listbox.set_value([])
listbox.Widget.selection_set(row) # tkinter code
elif event == 'Update':
rows = randint(5, 20)
print(rows)
data = [[f'Cell ({j:0>2d} ,{i:0>2d})' for i in range(rows)] for j in range(cols)]
for i in range(cols):
listbox = window[f'listbox {i}']
listbox.update(values=data[i])
listbox.Widget.configure(height=rows) # tkinter code to update height of listbox
window.refresh() # refresh required here
window['Column'].contents_changed() # update scrollbar
window['Rows'].update(f"Total rows = {rows}")
window.close()
table = window['-TABLE-'].Widget
table.tag_configure("RED", foreground='red')
table.tag_configure("GREEN", foreground='green')
# tag = "GREEN" or "RED"
ids = table.insert('', 'end', iid=i, text=new_row, values=new_row, tag=tag)
window['-TABLE-'].tree_ids.append(ids) # update key table
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.