I have OPEN and SAVE options on the right click menu which enables users to open or save selected multiple files in the ListBox output using PySimpleGui.. I am having errors to trigger those events.. Can you please help? Please refer to the code below I scripted but having errors.
import PySimpleGUI as sg
from tkinter.filedialog import asksaveasfile
from tkinter import *
import os
import threading
from time import sleep
from random import randint
def search(values, window):
"""Perform a search based on term and type"""
os.chdir('G:\MOTOR\DataExtracts/')
global results
# reset the results list
results.clear()
results = []
matches = 0 # count of records matched
records = 0 # count of records searched
window['-RESULTS-'].update(values=results)
window['-INFO-'].update(value='Searching for matches...')
# search for term and save new results
for root, _, files in os.walk(values['-PATH-']):
for file in files:
records +=1
if values['-ENDSWITH-'] and file.lower().endswith(values['-TERM-'].lower()):
results.append(f'{file}')
window['-RESULTS-'].update(results)
if values['-STARTSWITH-'] and file.lower().startswith(values['-TERM-'].lower()):
results.append(f'{file}')
window['-RESULTS-'].update(results)
if values['-CONTAINS-'] and values['-TERM-'].lower() in file.lower():
results.append(f'{file}')
window['-RESULTS-'].update(results)
matches += 1
window['-INFO-'].update('Enter a search term and press `Search`')
sg.PopupOK('Finished!') # print count of records of matched and searched in the popup(*)
def open_file(file_name):
# probably should add error handling here for when a default program cannot be found.(*)
# open selected files with read-only mode (check box or right click option)(*)
pass
def save_file(file_name):
# download selected files - one file or multiple files download at the same time (*)
# print downloading time or progress bar with downloaded files(*)
# print downloaded files logging info(*)
# create the main file search window
results = []
sg.change_look_and_feel('Black')
command = ['Open', 'Save']
cmd_layout = [[sg.Button(cmd, size=(10, 1))] for cmd in command]
layout = [
[sg.Text('Search Term', size=(11, 1)), sg.Input('', size=(40, 1), key='-TERM-'),
sg.Radio('Contains', group_id='search_type', size=(10, 1), default=True, key='-CONTAINS-'),
sg.Radio('StartsWith', group_id='search_type', size=(10, 1), key='-STARTSWITH-'),
sg.Radio('EndsWith', group_id='search_type', size=(10, 1), key='-ENDSWITH-')],
[sg.Text('Search Path', size=(11, 1)), sg.Combo(['ShdwProd/','Other/'],readonly=True, size=(38, 1), key='-PATH-', ), #display only folder name where files are located such as 'ShadowProd' and 'Ohter' in Combo
sg.Button('Search', size=(20, 1), key='-SEARCH-'),
sg.Button('Download', size=(20, 1), key='-DOWNLOAD-')],
[sg.Text('Enter a search term and press `Search`', key='-INFO-')],
[sg.Listbox(values=results, size=(100, 28), bind_return_key = True, enable_events=True, key='-RESULTS-', right_click_menu=['&Right', command])],
[sg.Help(key='-HELP-')]]
window = sg.Window('File Search Engine', layout=layout, finalize=True, return_keyboard_events=True)
window['-RESULTS-'].Widget.config(selectmode = sg.LISTBOX_SELECT_MODE_EXTENDED)
window['-RESULTS-'].expand(expand_x=True, expand_y=True)
# main event loop
while True:
event, values = window.read()
if event is None:
break
if event == '-SEARCH-':
search(values, window)
if event == '-RESULTS-' and len(values['-RESULTS-']):
if event == '-OPEN-': # need to code(*)
pass
if event == '-DOWNLOAD-': # need to code(*)
pass
if event == '-HELP-':
sg.Popup("My help message")
the (*) marks are what I am not sure about and having errors from..
Code revised as following
import os
import threading
from time import sleep
from random import randint
import PySimpleGUI as sg
def search(values, window):
"""Perform a search based on term and type"""
os.chdir("G:/MOTOR/DataExtracts/")
global results
# reset the results list
results = []
matches = 0 # count of records matched
records = 0 # count of records searched
window['-RESULTS-'].update(values=results)
window['-INFO-'].update(value='Searching for matches...')
window.refresh()
# search for term and save new results
for root, _, files in os.walk(values['-PATH-']): # path information of file missed here
for file in files:
records +=1
if any([values['-ENDSWITH-'] and file.lower().endswith(values['-TERM-'].lower()),
values['-STARTSWITH-'] and file.lower().startswith(values['-TERM-'].lower()),
values['-CONTAINS-'] and values['-TERM-'].lower() in file.lower()]):
results.append(f'{file}')
matches += 1
window['-RESULTS-'].update(results)
window['-INFO-'].update('Enter a search term and press `Search`')
window.refresh()
sg.PopupOK('Finished!') # print count of records of matched and searched in the popup(*)
def open_files(filenames):
"""
probably should add error handling here for when a default program cannot be found.(*)
open selected files with read-only mode (check box or right click option)(*)
"""
def save_files(filenames):
"""
download selected files - one file or multiple files download at the same time (*)
print downloading time or progress bar with downloaded files(*)
print downloaded files logging info(*)
"""
# create the main file search window
results = []
sg.theme('Black')
command = ['Open', 'Save']
cmd_layout = [[sg.Button(cmd, size=(10, 1))] for cmd in command]
layout = [
[sg.Text('Search Term', size=(11, 1)),
sg.Input('', size=(40, 1), key='-TERM-'),
sg.Radio('Contains', group_id='search_type', size=(10, 1), key='-CONTAINS-', default=True),
sg.Radio('StartsWith', group_id='search_type', size=(10, 1), key='-STARTSWITH-'),
sg.Radio('EndsWith', group_id='search_type', size=(10, 1), key='-ENDSWITH-')],
[sg.Text('Search Path', size=(11, 1)),
sg.Combo(['ShdwProd/','Other/'], default_value='ShdwProd/', readonly=True, size=(38, 1), key='-PATH-', ), #display only folder name where files are located such as 'ShadowProd' and 'Ohter' in Combo
sg.Button('Search', size=(20, 1), key='-SEARCH-'),
sg.Button('Download', size=(20, 1), key='-DOWNLOAD-')],
[sg.Text('Enter a search term and press `Search`', key='-INFO-')],
[sg.Listbox(values=results, size=(100, 28), select_mode=sg.LISTBOX_SELECT_MODE_EXTENDED, bind_return_key = True, enable_events=True, key='-RESULTS-', right_click_menu=['&Right', command], expand_x=True, expand_y=True)],
[sg.Help(key='-HELP-')]]
window = sg.Window('File Search Engine', layout=layout, finalize=True, return_keyboard_events=True)
# main event loop
while True:
event, values = window.read()
print(event, values)
if event == sg.WINDOW_CLOSED:
break
elif event == '-SEARCH-':
search(values, window)
elif event == '-RESULTS-' and len(values['-RESULTS-']):
pass
elif event == 'Open': # need to code(*)
open(values['-RESULTS-'])
elif event == 'Save': # need to code(*)
save(values['-RESULTS-'])
elif event == '-DOWNLOAD-': # need to code(*)
print(values['-RESULTS-'])
elif event == '-HELP-':
sg.Popup("My help message")
window.close()
Related
I have a project where I'm trying to build a simple time-clock using Python 3 on a Raspberry Pi. The Pi is running 64-bit Bullseye.
In my script, I create a window with two columns, one for entry and one for display. The entry side is working (as far as I have gone). The display side sorta works, and this is the issue.
The user will enter their code, press "Enter" to see their information, and then press "In" or "Out" for clocking in or out. When the user presses "In", I want to display a message that says either "Clocked In" or "Already Clocked in".
The issue is that the clocked-in message does not display. The statement that fails is the msg_elem.Update( .... If I run the Python debugger, the message is displayed, but not in "normal" running.
My question is What am I doing wrong?
This is a working example...
import sys, os, platform
import PySimpleGUI as sg
from PIL import Image, ImageTk
from time import sleep
import io
#
# Elements
# create the Elements we want to control outside the form
out_elem = sg.Text('', size=(55, 1), font=('Helvetica', 18), text_color='black',justification='center')
in_elem = sg.Input(size=(10,1), do_not_clear=True)
img_elem = sg.Image(size=(240,240),key="-IMAGE-")
msg_elem = sg.Text('', size=(65, 1), font=('Helvetica', 18), text_color='black',justification='center',key='-MSG-')
#
# Columns
button_column = [
[sg.Text("User Input"),in_elem],
[sg.ReadFormButton('1', size=(3,3)),
sg.ReadFormButton('2', size=(3,3)),
sg.ReadFormButton('3', size=(3,3)),
sg.ReadFormButton('Clear', size=(6,3))],
[sg.ReadFormButton('4', size=(3,3)),
sg.ReadFormButton('5', size=(3,3)),
sg.ReadFormButton('6', size=(3,3)),
sg.ReadFormButton('Enter', size=(6,3))],
[sg.ReadFormButton('7', size=(3,3)),
sg.ReadFormButton('8', size=(3,3)),
sg.ReadFormButton('9', size=(3,3)),
sg.ReadFormButton('Quit', size=(6,3))],
[sg.T(''), sg.T(' ' * 8),
sg.ReadFormButton('0', size=(3,3))],
[sg.T('')],
[sg.ReadFormButton('In', size=(13,3)),
sg.ReadFormButton('Out', size=(13,3)),]
]
display_column = [
[sg.Text("User Details")],
[out_elem],
[sg.T(' ' * 30), img_elem],
[msg_elem],
]
#
layout = [
[sg.Column(button_column),
sg.VSeperator(),
sg.Column(display_column,justification='center',vertical_alignment='top')]
]
form = sg.Window('Time Clock', layout, auto_size_buttons=False, size=(800,480))
keys_entered = ''
while True:
button, values = form.Read()
if button is None:
form["-IMAGE-"].update()
out_elem.Update( " " )
break
elif button == 'Clear':
keys_entered = ''
pcpid = ''
empid = ''
form["-IMAGE-"].update()
in_elem.Update(keys_entered)
out_elem.Update( " " )
msg_elem.Update( " " )
elif button in '1234567890':
keys_entered = in_elem.Get()
keys_entered += button
elif button == 'Enter':
keys_entered = '123'
first_name = 'Mike'
last_name = 'Retiredguy'
empid = 12345
im1 = Image.open( 'mike.png' )
im1.thumbnail((240,240))
bio = io.BytesIO()
im1.save( bio, format="PNG")
empimage = bio.getvalue()
form["-IMAGE-"].update( empimage )
dsplAns = f"{empid} - {last_name}, {first_name}"
out_elem.Update( dsplAns )
elif button == 'In':
# import pdb; pdb.set_trace()
sqlAns = 1 # User already clocked in
if sqlAns > 0:
msg_elem.Update( "...is already clocked in! (A)" ) # <=== THIS IS WHAT FAILS
else:
msg_elem.Update( "...is clocked in! (B)" ) # <=== THIS IS WHAT FAILS
sleep(10)
# Clear input
keys_entered = ''
pcpid = ''
empid = ''
form["-IMAGE-"].update()
in_elem.Update(keys_entered)
out_elem.Update( " " )
msg_elem.Update( " " )
elif button == 'Quit':
sys.exit(0)
in_elem.Update(keys_entered)
#
# ###EOF###
I have tried this on the RPi, and on a Virtual system with Debian (not Pi) linux. Both give me the same result. I've searched here on Stack Overflow, and Google in general, and I think I'm doing it correctly, but failing.
The method msg_elem.Update just update the architecture of PySimpleGUI, not the GUI. Need to call window.refresh() before sleep(10) if you want the GUI updated immediately, not until back to window.read(). sleep(10) take long time for GUI to wait, so it will show "Not Responding".
Demo Code
from time import sleep
import threading
import PySimpleGUI as sg
def func(window, value):
global running
message = f'You clicked the button "{value}", this message will be cleared after 3s !'
window.write_event_value('Update', message)
sleep(3)
window.write_event_value('Update', '')
running = False
sg.set_options(font=('Courier New', 12))
layout = [
[sg.Button('Hello'), sg.Button('World')],
[sg.Text('', size=80, key='State')],
]
window = sg.Window('Title', layout)
running = False
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event in ('Hello', 'World') and not running:
running = True
threading.Thread(target=func, args=(window, event), daemon=True).start()
elif event == 'Update':
message = values[event]
window['State'].update(message)
window.close()
I'm having trouble trying to make pysimplegui work as I want.
I would like to open the program, enter my information, run "Extract",
let the script finish (it shows details in the output). Then on the same window, I would like to run another one using the same button "Extract" once the first script has finished running.
The best I can do is loop it and it opens a new window and closes the first.
How do I get it to just stay open and allow me to do another search without closing when the script is finished?
In other words, I want to open 1 GUI window and run multiple scripts using different parameters "keywords" or start/end dates. One after the other.
guivalues = ['', '', '', "localhost", "root", 'PASSWORD', '', ''];
while True:
sg.theme('Dark2')
layout = [
[sg.Text('Enter multiple Keywords, comma to separate')],
[sg.Text('Keywords', size=(20, 1)), sg.InputText()],
[sg.Text('Start Date YYYY-MM-DD: ', size=(20, 1)), sg.InputText(guivalues[1])],
[sg.Text('End Date YYYY-MM-DD: ', size=(20, 1)), sg.InputText(guivalues[2])],
[sg.Text('Host (usually localhost): ', size=(20, 1)), sg.InputText(guivalues[3])],
[sg.Text('User (usually root): ', size=(20, 1)), sg.InputText(guivalues[4])],
[sg.Text('mySQL password: ', size=(20, 1)), sg.InputText(guivalues[5])],
[sg.Text('Database: ', size=(20, 1)), sg.InputText(guivalues[6])],
[sg.Text('Table: ', size=(20, 1)), sg.InputText(guivalues[7])],
[sg.Output(size=(70, 15))],
[sg.Button('Extract'), sg.Button('Close')]
]
window = sg.Window('Python to SQL', layout)
event, guivalues = window.read()
if event == 'Close':
break
window.Close()
if event == 'Extract':
XXX EXTRACTION CODE XXXXX
window.read()
window.Close()
ISSUE RESOLVED
sg.theme('Dark2')
layout = [
[sg.Text('Enter multiple Keywords, comma to separate')],
[sg.Button('Extract', bind_return_key=True ), sg.Button('Close')]
]
window = sg.Window('Python to SQL', layout)
while True:
event, guivalues = window.read()
if event in (None, 'Quit'):
break
if event == 'Close':
break
if event == 'Extract':
mydb = mysql.connector.connect(
The key is not looping the GUI window. Use the "While True" loop for the script doing the backend work. And within the while loop, you "read" the window for conditional "event" statements. THEN you put the conditionals within the loop. In other words
#GUI Stuff#
then
While True:
event, guivalues = window.read()
then
Conditionals
import sys
import PySimpleGUI as sg
import os
sg.theme('SandyBeach')
layout = [
[sg.Text('Enter admin username and password')],
[sg.Text('username', size =(7, 1)), sg.InputText()],
[sg.Text('password', size =(7, 1)), sg.InputText()],
[sg.Submit(), sg.Cancel()],
]
window = sg.Window('Admin loggin', layout, margins=(10, 10))
event, values = window.read()
window.close()
def loggin():
global success
success = False
file = open('psw.txt','r')
for i in file:
a,b = i.split(",")
b = b.strip()
if (a == values[0] and b == values[1]):
success = True
else:
print("wrong")
loggin()
if success == False:
pass
print("next stage")
I'm trying to use pysimplegui, but I can't re-run the window. I'm trying to figure out how to call window(), so I can try logging in if you mess up. But whenever I try to run window(), it gives me an error that I can't open a window that's already been open. Any workarounds or ways to restart the scripts completely?
You cannot re-use the layout by instance of element, call class of element required.
To recall a window, by using function call will be better.
import PySimpleGUI as sg
def get_codes(filename):
with open(filename, 'rt') as f:
lines = f.readlines()
codes = []
for line in lines:
a, b = line.split(',')
codes.append((a.strip(), b.strip()))
return codes
def login(codes):
sg.theme('SandyBeach')
layout = [
[sg.Text('Enter admin username and password')],
[sg.Text('username', size =(7, 1)), sg.InputText()],
[sg.Text('password', size =(7, 1)), sg.InputText()],
[sg.Submit(), sg.Cancel()],
]
window = sg.Window('Admin loggin', layout, margins=(10, 10))
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Cancel'):
print('Login Cancelled')
success = None
elif event == 'Submit':
if (values[0], values[1]) in codes:
success = True
print("Login Successful")
else:
success = False
print("Login Failed")
break
window.close()
return success
password_file = 'psw.txt'
codes = get_codes(password_file)
success = login(codes)
Here is different way. I added the os module so that you get a relative path, which will then work on any computer.
import PySimpleGUI as sg
import os
def login():
sg.theme('SandyBeach')
layout = [
[sg.Text('Enter admin username and password')],
[sg.Text('username', size =(7, 1)), sg.InputText()],
[sg.Text('password', size =(7, 1)), sg.InputText()],
[sg.Submit(key='Submit'), sg.Cancel(key='Cancel')],
]
window = sg.Window('Admin loggin', layout, margins=(10, 10))
while True:
event, values = window.read()
if event == 'Cancel' or event == sg.WIN_CLOSED:
break
else:
filename = os.path.join(os.path.dirname(__file__), 'psw.txt') # <-- Use this so that you have a relative path.
# This way it works on any machine
with open(filename, 'r') as file:
file = open(filename, 'r')
for i in file:
a,b = i.split(",")
b = b.strip()
if (a == values[0] and b == values[1]):
sg.popup_ok("Welcome to your account!")
window.close()
break
else:
sg.popup_error("Incorrect credentials. Try again.")
continue
login()
Firstly, PySimpleGUI is amazing! However, I cannot figure out how to show all the files in a folder when using folderbrowse() ?
Alternatively, would it be possible to print the filenames in the selected in an outbox box? Please could I get some guidance on this.
Thanks!
FileBrowse() and FolderBrowse() are different widgets.
FolderBrowse() is for selecting only folder so it doesn't display files.
FileBrowse() is for selecting file so it show files and folders (but you can't select folder to get it).
FileBrowse() gives full path to selected folder and later you should use
os.listdir(folder) to get names for all files and folders in selected folder (but without names in subfolders)
os.walk(folder) to get for all files and folders in this folder and subfolders.
glob.glob(pattern) to get only some names - ie. glob.glob(f"{folder}/*.png")
and when you get names then you can print in console or update text in widget.
This minimal example display filenames in console after clicking Submit
import PySimpleGUI as sg
import os
#help(sg.FolderBrowse)
#help(sg.FileBrowse)
layout = [
[sg.Input(), sg.FileBrowse('FileBrowse')],
[sg.Input(), sg.FolderBrowse('FolderBrowse')],
[sg.Submit(), sg.Cancel()],
]
window = sg.Window('Test', layout)
while True:
event, values = window.read()
#print('event:', event)
#print('values:', values)
print('FolderBrowse:', values['FolderBrowse'])
print('FileBrowse:', values['FileBrowse'])
if event is None or event == 'Cancel':
break
if event == 'Submit':
# if folder was not selected then use current folder `.`
foldername = values['FolderBrowse'] or '.'
filenames = os.listdir(foldername)
print('folder:', foldername)
print('files:', filenames)
print("\n".join(filenames))
window.close()
Similar way you can put text in some widget - ie. MultiLine() - after pressing Submit
import PySimpleGUI as sg
import os
layout = [
[sg.Input(), sg.FolderBrowse('FolderBrowse')],
[sg.Submit(), sg.Cancel()],
[sg.Text('Files')],
[sg.Multiline(key='files', size=(60,30), autoscroll=True)],
]
window = sg.Window('Test', layout)
while True:
event, values = window.read()
if event is None or event == 'Cancel':
break
if event == 'Submit':
foldername = values['FolderBrowse'] or '.'
filenames = os.listdir(foldername)
# it uses `key='files'` to access `Multiline` widget
window['files'].update("\n".join(filenames))
window.close()
BTW: system may give filenames in order of creation so you may have to sort them
filenames = sorted(os.listdir(foldername))
EDIT:
To get filenames without Submit you may have to use normal Button which will execute code with foldername = PopupGetFolder(..., no_window=True).
import PySimpleGUI as sg
import os
layout = [
[sg.Input(), sg.Button('FolderBrowse')],
[sg.Text('Files')],
[sg.Multiline(key='files', size=(60,30), autoscroll=True)],
[sg.Exit()],
]
window = sg.Window('Test', layout)
while True:
event, values = window.read()
print(event)
if event is None or event == 'Exit':
window.close()
break
if event == 'FolderBrowse':
foldername = sg.PopupGetFolder('Select folder', no_window=True)
if foldername: # `None` when clicked `Cancel` - so I skip it
filenames = sorted(os.listdir(foldername))
# it use `key='files'` to `Multiline` widget
window['files'].update("\n".join(filenames))
I created a FileSaveAs button in my PySimpleGUI application, and defined the available file_types to be 'png' and 'jpg', but I have no way of knowing which of these two options was selected by the user. In other words, unless explicitly entered by the user, the value I get does not include the file extension.
Here's the code:
import PySimpleGUI as sg
layout = [[
sg.InputText(visible=False, enable_events=True, key='fig_path'),
sg.FileSaveAs(
key='fig_save',
file_types=(('PNG', '.png'), ('JPG', '.jpg')), # TODO: better names
)
]]
window = sg.Window('Demo Application', layout, finalize=True)
fig_canvas_agg = None
while True: # Event Loop
event, values = window.Read()
if (event == 'fig_path') and (values['fig_path'] != ''):
print('Saving to:', values['fig_path'])
if event is None:
break
Example:
In the above case, the value will be "[some path]\Test\hello", instead of ending with "hello.png".
Any way of either getting the returned path to include the extension, or getting the extension value separately?
Add defaultextension="*.*" to tk.filedialog.asksaveasfilename ()
It's around line 3332 in ver 4.30.0 of pysimplegui.py