pysimplegui keys not being assigned to elements correctly - python

I am trying to dynamically assign keys using pysimplegui. A simple example is updating a text field when a folder is selected.
When the layout contains two elements in each row, the text field updates correctly. However adding a third element results in the text box not updating.
import PySimpleGUI as sg
layout = []
elements = ["one", "two", "three"]
for element in elements:
text = f"text_{element}"
folder = f"folder_{element}"
check = f"check_{element}"
layout.append([sg.FolderBrowse(key = folder), sg.Text(key = text, size=(50, 1))])
#layout.append([sg.FolderBrowse(key = folder), sg.Text(key = text, size=(50, 1)), sg.Checkbox(element, key=check)])
#layout.append([sg.FolderBrowse(key = folder), sg.Text(key = text, size=(50, 1)), sg.Text(element, key=check)])
layout.append([sg.Button('Show'), sg.Cancel()])
window = sg.Window("GUI", layout)
while(True):
event, values = window.read()
if event in (None, 'Cancel'):
break
print(values)
for element in elements:
text = f"text_{element}"
folder = f"folder_{element}"
values[text] = values[folder]
window.close()

Thanks to MikefromPSG, using the target param worked:
layout.append([sg.FolderBrowse(target = text), sg.Text(key = text, size=(50, 1)), sg.Checkbox(element, key=check)])

Related

Is there a way to clear a tree element in PySimpleGUI before adding new data?

I have a window with a tree element that shows files and folders when I enter a path. I need this tree to clear if I enter a new path. At the moment the tree appends the files/folders from each path I enter. Is there a way to refresh the tree element before entering new information into it?
My code is similar to this:
import PySimpleGUI as sg
import os
starting_path = os.getcwd()
treedata = sg.TreeData()
folder_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABnUlEQVQ4y8WSv2rUQRSFv7vZgJFFsQg2EkWb4AvEJ8hqKVilSmFn3iNvIAp21oIW9haihBRKiqwElMVsIJjNrprsOr/5dyzml3UhEQIWHhjmcpn7zblw4B9lJ8Xag9mlmQb3AJzX3tOX8Tngzg349q7t5xcfzpKGhOFHnjx+9qLTzW8wsmFTL2Gzk7Y2O/k9kCbtwUZbV+Zvo8Md3PALrjoiqsKSR9ljpAJpwOsNtlfXfRvoNU8Arr/NsVo0ry5z4dZN5hoGqEzYDChBOoKwS/vSq0XW3y5NAI/uN1cvLqzQur4MCpBGEEd1PQDfQ74HYR+LfeQOAOYAmgAmbly+dgfid5CHPIKqC74L8RDyGPIYy7+QQjFWa7ICsQ8SpB/IfcJSDVMAJUwJkYDMNOEPIBxA/gnuMyYPijXAI3lMse7FGnIKsIuqrxgRSeXOoYZUCI8pIKW/OHA7kD2YYcpAKgM5ABXk4qSsdJaDOMCsgTIYAlL5TQFTyUIZDmev0N/bnwqnylEBQS45UKnHx/lUlFvA3fo+jwR8ALb47/oNma38cuqiJ9AAAAAASUVORK5CYII='
file_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABU0lEQVQ4y52TzStEURiHn/ecc6XG54JSdlMkNhYWsiILS0lsJaUsLW2Mv8CfIDtr2VtbY4GUEvmIZnKbZsY977Uwt2HcyW1+dTZvt6fn9557BGB+aaNQKBR2ifkbgWR+cX13ubO1svz++niVTA1ArDHDg91UahHFsMxbKWycYsjze4muTsP64vT43v7hSf/A0FgdjQPQWAmco68nB+T+SFSqNUQgcIbN1bn8Z3RwvL22MAvcu8TACFgrpMVZ4aUYcn77BMDkxGgemAGOHIBXxRjBWZMKoCPA2h6qEUSRR2MF6GxUUMUaIUgBCNTnAcm3H2G5YQfgvccYIXAtDH7FoKq/AaqKlbrBj2trFVXfBPAea4SOIIsBeN9kkCwxsNkAqRWy7+B7Z00G3xVc2wZeMSI4S7sVYkSk5Z/4PyBWROqvox3A28PN2cjUwinQC9QyckKALxj4kv2auK0xAAAAAElFTkSuQmCC'
def add_files_in_folder(parent, dirname):
files = os.listdir(dirname)
for f in files:
fullname = os.path.join(dirname, f)
if os.path.isdir(fullname): # if it's a folder, add folder and recurse
treedata.Insert(parent, fullname, f, values=[], icon=folder_icon)
add_files_in_folder(fullname, fullname)
else:
treedata.Insert(parent, fullname, f, values=[os.stat(fullname).st_size], icon=file_icon)
def main_window():
column_one = sg.Column([
[sg.Tree(data=treedata,
headings=['Size', ],
auto_size_columns=True,
select_mode=sg.TABLE_SELECT_MODE_EXTENDED,
num_rows=20,
col0_width=40,
key='-TREE-',
show_expanded=False,
enable_events=True,
expand_x=True,
expand_y=True,
), ],
[sg.B('Open', k='-OPEN-'), sg.B('Add', k='-ADD-')],
])
layout = [
[sg.T('Some text')],
[sg.HorizontalSeparator()],
[sg.T('Select the files / folders')],
[column_one],
[sg.B('Cancel', k='-EXIT-')]
]
return sg.Window('some window', layout, resizable=True, finalize=True)
window = main_window()
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, '-EXIT-'):
break
print(event, values)
if event == '-OPEN-':
starting_path = sg.popup_get_folder('Folder to display')
add_files_in_folder('', starting_path)
window['-TREE-'].update(values=treedata)
window.close()
Any help is appreciated!
Following code demo how to create/clear/append data to treedata for Table element. Here all item added as children of root ''.
import PySimpleGUI as sg
def add_data(treedata, offset):
default = [[str((i+1)*(j+offset+1)) for i in range(9)] for j in range(9)]
for i, value in enumerate(default):
treedata.insert('', i+offset+1, i+offset+1, value)
return treedata
treedata = sg.TreeData()
treedata = add_data(treedata, 0)
index = 10
layout = [
[sg.Tree(
data=treedata,
headings=[str(i+1) for i in range(9)],
col0_heading='9x9',
col0_width=5,
num_rows=20,
key='-TREE-',
)],
[sg.Button('Clear'), sg.Button('Reset'), sg.Button('Append')],
]
window = sg.Window('Title', layout, finalize=True)
tree = window['-TREE-']
tree.Widget.column('#0', anchor='e')
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'Clear':
treedata = sg.TreeData()
tree.update(values=treedata)
index = 0
elif event == 'Reset':
treedata = add_data(sg.TreeData(), 0)
tree.update(values=treedata)
index = 10
elif event == 'Append':
treedata = add_data(treedata, index)
tree.update(values=treedata)
index += 10
window.close()

Make an editable table in PySimpleGUI?

Hello I am using a Table element from PySimpleGUI. I would like for the user to be able to edit the data inside it.
I have seen some mentions of it. Is it possible? This guy was using PySimpleGUIQt, while I am using the PySimpleGUI on top of tkinter.
It's much difficult for me to build it by pure PySimpleGUI code.
If go it with new class inherited from sg.Table, some variables as argument or global variables not required.
Here, colors of cell not considered in Entry.
All tkinter code in function edit_cell of following example.
import PySimpleGUI as sg
import random, string
# ------ Some functions to help generate data for the table ------
def word():
return ''.join(random.choice(string.ascii_lowercase) for i in range(10))
def number(max_val=1000):
return random.randint(0, max_val)
def make_table(num_rows, num_cols):
data = [[j for j in range(num_cols)] for i in range(num_rows)]
data[0] = [word() for _ in range(num_cols)]
for i in range(0, num_rows):
data[i] = [i, word(), *[number() for i in range(num_cols - 1)]]
return data
def edit_cell(window, key, row, col, justify='left'):
global textvariable, edit
def callback(event, row, col, text, key):
global edit
widget = event.widget
if key == 'Return':
text = widget.get()
print(text)
widget.destroy()
widget.master.destroy()
values = list(table.item(row, 'values'))
values[col] = text
table.item(row, values=values)
edit = False
if edit or row <= 0:
return
edit = True
root = window.TKroot
table = window[key].Widget
text = table.item(row, "values")[col]
x, y, width, height = table.bbox(row, col)
frame = sg.tk.Frame(root)
frame.place(x=x, y=y, anchor="nw", width=width, height=height)
textvariable = sg.tk.StringVar()
textvariable.set(text)
entry = sg.tk.Entry(frame, textvariable=textvariable, justify=justify)
entry.pack()
entry.select_range(0, sg.tk.END)
entry.icursor(sg.tk.END)
entry.focus_force()
entry.bind("<Return>", lambda e, r=row, c=col, t=text, k='Return':callback(e, r, c, t, k))
entry.bind("<Escape>", lambda e, r=row, c=col, t=text, k='Escape':callback(e, r, c, t, k))
def main_example1():
global edit
edit = False
# ------ Make the Table Data ------
# sg.Print('Creating table...')
data = make_table(num_rows=1_000, num_cols=6)
# headings = [str(data[0][x])+' ..' for x in range(len(data[0]))]
headings = [f'Col {col}' for col in range(len(data[0]))]
# sg.Print('Done creating table. Creating GUI...')
sg.set_options(dpi_awareness=True)
layout = [[sg.Table(values=data, headings=headings, max_col_width=25,
auto_size_columns=True,
# display_row_numbers=True,
justification='right',
num_rows=20,
alternating_row_color=sg.theme_button_color()[1],
key='-TABLE-',
# selected_row_colors='red on yellow',
# enable_events=True,
# select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True,
expand_y=True,
enable_click_events=True, # Comment out to not enable header and other clicks
)],
[sg.Button('Read'), sg.Button('Double'), sg.Button('Change Colors')],
[sg.Text('Cell clicked:'), sg.T(k='-CLICKED-')]]
window = sg.Window('Table Element - Example 1', layout, resizable=True, finalize=True)
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
elif isinstance(event, tuple):
cell = row, col = event[2]
window['-CLICKED-'].update(cell)
edit_cell(window, '-TABLE-', row+1, col, justify='right')
window.close()
main_example1()
I would strongly suggest use MutiLine for showing table, which is much simpler to manage and can be easily updated etc. The Same table effects can be achieved by less lines of code. Giving a sample code here from my own database program with uses Sqlite3 table.
def ViewTable (TableNme):
try: # LOAD TABLE FROM Sqlite3 Database
txt2="SELECT * FROM {} ".format(TableNme)
cursor.execute(txt2)
##Include headers###
txt2='S.No '
for j in fields: txt2=txt2+j +" "
txt2=txt2+'\n'
#converts the table into string separated by Comma
#fine as long as table is not very massive running more than 3 Mbs, having more than 1000 records
while True:
h1=cursor.fetchone()
if not h1: break
for j in h1:txt2=txt2+str(j)+" "
txt2=txt2+'\n'
#sg.popup('Archieved table', txt2)
#Define text to load by text= before calling me
layout2 = [[sg.Multiline(txt2,size=(28,28),key='-Items-'),],[sg.Ok()] ]
sg.Window('Scroll to see the whole database', layout2,finalize=True).read(close=True)
except:
sg.popup_error('Failed','Access denied or Memory full.')

How to use right_click_menu with multiple InputText elements?

How do you distinguish between multiple elements on the same window when using right_click_menu in PySimpleGUI? For example, with the code below, how do I tell which one of the two InputText elements I am trying to use the right_click_menu with? If I copy something to the clipboard and then right-click 'Paste' on one of the Input fields, the same data will appear in both fields. When I right-click on one of the InputText fields, how can I write the code to identify which one I'm on?:
import PySimpleGUI as sg
INPUT1 = 'INPUT1'
INPUT2 = 'INPUT2'
right_click_menu = ['',['Paste']]
layout = [
[sg.Text('Input1'), sg.InputText('', key='INPUT1', right_click_menu = right_click_menu)],
[sg.Text('Input2'), sg.InputText('', key='INPUT2', right_click_menu = right_click_menu)],
[sg.Button(' OK '), sg.Button(' Exit ')]
]
window = sg.Window('Multiple Elements', layout)
input1:sg.InputText = window[INPUT1]
input2:sg.InputText = window[INPUT2]
while True:
event, values = window.read()
if event in (' Exit ', None):
break
if event == 'Paste':
# How to tell whether I am right-clicking on INPUT1 or INPUT2?
# With just one Input element, I could just do this:
input1.Widget.insert(sg.tk.INSERT, window.TKroot.clipboard_get())
# What do I do when there is a second InputText field?
# Below won't work because I'll get the same text pasted into both fields.
input2.Widget.insert(sg.tk.INSERT, window.TKroot.clipboard_get())
if event == ' OK ':
pass
#Do blah
window.close()
Refer https://pysimplegui.readthedocs.io/en/latest/#keys-for-menus
A key is indicated by adding :: after a menu entry, followed by the key.
import PySimpleGUI as sg
INPUT1 = 'INPUT1'
INPUT2 = 'INPUT2'
right_click_menu = [['',[f'Paste::Paste {i}']] for i in range(2)]
layout = [
[sg.Text('Input1'), sg.InputText('', key='INPUT1', right_click_menu = right_click_menu[0])],
[sg.Text('Input2'), sg.InputText('', key='INPUT2', right_click_menu = right_click_menu[1])],
[sg.Button(' OK '), sg.Button(' Exit ')]
]
window = sg.Window('Multiple Elements', layout)
input1:sg.InputText = window[INPUT1]
input2:sg.InputText = window[INPUT2]
while True:
event, values = window.read()
if event in (' Exit ', None):
break
if event.startswith('Paste'):
element = input1 if event.split()[1] == '0' else input2
element.Widget.insert(sg.tk.INSERT, window.TKroot.clipboard_get())
window.close()

Python Script/Programm to Search & Display an Image from Local Folder

I'm an absolute beginner in python and trying to build a script/programm that searches images from a folder by their name (meaning that it would need a search bar) and displays these image in the same window.
Think of it as sort of a phonebook where I have stored 100 names and pictures and I want to type in "Henry" to see Henry's picture.
On my search for a software that achieves this, I've come accross something similar:
# img_viewer.py
import PySimpleGUI as sg
import os.path
# First the window layout in 2 columns
file_list_column = [
[
sg.Text("Image Folder"),
sg.In(size=(25, 1), enable_events=True, key="-FOLDER-"),
sg.FolderBrowse(),
],
[
sg.Listbox(
values=[], enable_events=True, size=(40, 20), key="-FILE LIST-"
)
],
]
# For now will only show the name of the file that was chosen
image_viewer_column = [
[sg.Text("Choose an image from list on left:")],
[sg.Text(size=(40, 1), key="-TOUT-")],
[sg.Image(key="-IMAGE-")],
]
# ----- Full layout -----
layout = [
[
sg.Column(file_list_column),
sg.VSeperator(),
sg.Column(image_viewer_column),
]
]
window = sg.Window("Image Viewer", layout)
# Run the Event Loop
while True:
event, values = window.read()
if event == "Exit" or event == sg.WIN_CLOSED:
break
# Folder name was filled in, make a list of files in the folder
if event == "-FOLDER-":
folder = values["-FOLDER-"]
try:
# Get list of files in folder
file_list = os.listdir(folder)
except:
file_list = []
fnames = [
f
for f in file_list
if os.path.isfile(os.path.join(folder, f))
and f.lower().endswith((".png", ".gif"))
]
window["-FILE LIST-"].update(fnames)
elif event == "-FILE LIST-": # A file was chosen from the listbox
try:
filename = os.path.join(
values["-FOLDER-"], values["-FILE LIST-"][0]
)
window["-TOUT-"].update(filename)
window["-IMAGE-"].update(filename=filename)
except:
pass
window.close()
This is almost perfect, but I would need to implement a search bar instead of clicking through the names as I will have ~20.000 entries and clicking through them is just not viable.
Any idea how I would go about achieving this?
Just one file after filtered not so good, My suggestion
Add one more sg.InputText() as filter for filename.ext
Event loop
Event for New holder or new filter
Get list of filenames of all files in folder, maybe also filtered by PNG, GIF.
Build new list to update 'sg.Listbox` if filter_string.lower() in filename_lower()
Event for listbox
Update sg.Image by clicked filename in listbox.
Update - Working sample code
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()

Tkinter / Python - Aligning Text in Character String (Project for Class)

Am loading a listbox in TKinter. The string is a collection of fields from a matrix. 4 fields, 1 date, 1 float, and 2 text.
However, because of proportional fonts, I cannot get the text in each line to line up into columns.
Here is my current code
from tkinter import *
from tkinter import ttk
from datetime import *
import locale
# for currency display, set the locale
locale.setlocale( locale.LC_ALL, '' )
class main_gui(Frame):
def __init__(self): #Intialize key variables and call the main screen.
self.Ledgerdata = [ ]
print ("main_gui __init__")
self.load_data()
self.main_screen()
def load_data(self):
# stub for testing - to be rewritten to read external file and load into Ledgerdata.
entry = []
entry.append("4/17/2016, 24, 1, Doctor")
entry.append("4/18/2016, 32, 1, Mechanic whose name is bill")
entry.append("4/19/2016, 45, 1, Grocery")
entry.append("4/11/2016, 19, 1, Puppet")
entry.append("4/17/2016, 119.50, 1, Johns Computer House")
entry.append("4/11/2016, 1250, 1, Sidney Barthalmew")
for entry_index in range(len(entry)):
entry[entry_index] = entry[entry_index].split(",")
date_conversion = entry[entry_index][0].split("/") # Convert date from text string
entry[entry_index][0] = date(int(date_conversion[2]), # to datetime variable
int(date_conversion[0]),
int(date_conversion[1]))
self.Ledgerdata.append((entry[entry_index][0], # Add transaction to Ledgerdata
float(entry[entry_index][1]),
int(entry[entry_index][2]),
entry[entry_index][3]))
#====== Data from file is read and loaded into Ledgerdata
#main_screen - main display screen for Ledgerdata
# Will show list of transactions, total for each category,
# with buttons to Add, Edit, Delete, Create Summary and Exit
def main_screen(self):
root = Tk() # reference to the GUI toolkit
main_window = ttk.Frame(root)
main_window.grid(row=0,column=0) # overall Frame
display_frame = ttk.Frame(main_window) # list display frame
display_frame.grid(column=0, row = 0)
option_frame = ttk.Frame(main_window) # selection frame - to be written.
option_frame.grid(column=0, row=15, columnspan=8)
summary_frame = ttk.Frame(main_window)
summary_frame.grid(column = 10, row = 0)
summary_frame.columnconfigure(0, minsize=50)
summary_frame.columnconfigure(1, minsize=250)
#Start display frame
list_header = Label(display_frame, width = 100, # Header line for List
font = 12,anchor = W, # NEEDS CLEANUP
text = " Date Amount Description Code")
list_header.grid(row=1)
list_of_transactions = Listbox(display_frame,width =100, height = 20, font = 12) #Generate listbox widget
list_of_transactions.grid(row=2, column = 0)
# Loop for number of transactions in Ledgerdata. Loop adds formatted transaction info to list_of_transactions
# liststring object for display
for x in range(len(self.Ledgerdata)):
output_string = '{0:10} {1:>8} {2:<50} {3:<40}'.format(
str(self.Ledgerdata[x][0].strftime("%m/%d/%y"))
,locale.currency(self.Ledgerdata[x][1], grouping=True)
,(self.Ledgerdata[x][3])
,(self.Ledgerdata[x][2]))
list_of_transactions.insert(x,output_string)
list_of_transactions.grid(row=x, column = 0)
#main_window.pack()
mainloop()
main_gui()
Anyone know how to get elements in strings to text to maintain the same width???
Thanks as always,
Darrell
EDITTED - Per request, added entire code segment that can be run. I had to take this out of my main program, eliminate several parts that are not relevant to the problem at hand, and make some changes that do not affect the problem at hand. So this is just a standalone testbed.

Categories