How to display files in folder when using PysimpleGUI FileBrowse() function? - python

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))

Related

convert path to excel using python and pysimplegui gives typeError [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 months ago.
Improve this question
I am trying to write a script which writes path data to an excel file. The function is working, I just want to create an simple GUI using PySimpleGUI where the user can give a path. The path variable is used in the function. Now I have the error:
str_path = int(values['-INF1-'])
TypeError: tuple indices must be integers or slices, not str
This is my code:
import PySimpleGUI as sg
import ctypes
import platform
import os
import pandas as pd
from pathlib import Path
from docxtpl import DocxTemplate
def make_dpi_aware():
if int(platform.release()) >= 8:
ctypes.windll.shcore.SetProcessDpiAwareness(True)
make_dpi_aware()
sg.LOOK_AND_FEEL_TABLE['MyCreatedTheme'] = {'BACKGROUND': '#DCD7C9',
'TEXT': '#000000',
'INPUT': '#E9DAC1',
'TEXT_INPUT': '#000000',
'SCROLL': '#99CC99',
'BUTTON': ('#000000', '#54BAB9'),
'PROGRESS': ('#D1826B', '#CC8019'),
'BORDER': 1, 'SLIDER_DEPTH': 0,
'PROGRESS_DEPTH': 0, }
sg.theme('MyCreatedTheme')
layout = [
[sg.T('Voer een pad in: '), sg.Input(key='-INF1-')],
[sg.FolderBrowse('Opslaan in', key="-IN-")],
[sg.Button("Open excel bestand"), sg.Exit()]
]
window = sg.Window('Pad naar excel generator', layout, element_justification="right", modal=True, font='Any 12')
values = window.read()
str_path = int(values['-INF1-'])
path = Path(str_path)
def list_files(path):
files = []
for r, d, f in os.walk(path):
for file in f:
files.append(os.path.join(r, file))
df = pd.DataFrame(files, columns = ['path'])
df['filename'] = df['path'].str.split('\\').str[-1]
df['path'] = df['path'].str.replace(r'\\[^\\]*$', '')
df.to_excel('files.xlsx', index = False)
return df
list_files(path)
while True:
event = window.read()
if event == sg.WIN_CLOSED or event == "Exit":
break
if event == "Open excel bestand":
list_files(values['-INF1-'])
output_path = Path(values["-IN-"]) / f"filestopath.docx"
doc = DocxTemplate(output_path)
doc.save(output_path)
os.startfile(output_path)
window.close()
Can someone explain to me what is going wrong? Any help would be appreciated!
This is how PySimpleGUI docs tell us to read event and values from Window:
event, values = window.read()
Instead you do (in different places):
values = window.read()
...
event = window.read()
In your code values/event is assigned a tuple of (<EVENT>, <VALUES>), which you try to process as if it were PySimpleGUI object.
If you need only one part of the tuple, do this instead:
_, values = window.read()
...
event, _ = window.read()

Pre-select multiple files in a QFileDialog

When a "choose files" dialog is displayed I want to pre-select files in a project which are already configured as being "part of" that project, so the user can select new files OR unselect existing (i.e. previously chosen) files.
This answer suggests multiple selection should be possible.
For this MRE, please make 3 files and put them in a suitable ref_dir:
from PyQt5 import QtWidgets
import sys
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.handle_button)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
def handle_button(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
ref_dir = 'D:\\temp'
files_list = ['file1.txt', 'file2.txt', 'file3.txt']
fd = QtWidgets.QFileDialog(None, 'Choose project files', ref_dir, '(*.txt)')
fd.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
fd.setOptions(options)
# fd.setVisible(True)
for file in files_list:
print(f'selecting file |{file}|')
fd.selectFile(file)
string_list = fd.exec()
print(f'string list {string_list}')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Unfortunately, despite ExistingFiles having been chosen as the file mode, I find that it is only the last file selected which has the selection... but I want all three to be selected when the dialog is displayed.
I tried experimenting with setVisible to see whether the multiple selection could be achieved somehow after the dialog is displayed, but this didn't work.
Since a non-native file dialog is being used, we can access its child widgets to control its behavior.
At first I thought about using the selection model of the item views, but this won't update the line edit, which is responsible of checking if the files exist and enabling the Ok button in that case; considering this, the obvious solution is to directly update the line edit instead:
def handle_button(self):
# ...
existing = []
for file in files_list:
if fd.directory().exists(file):
existing.append('"{}"'.format(file))
lineEdit = fd.findChild(QtWidgets.QLineEdit, 'fileNameEdit')
lineEdit.setText(' '.join(existing))
if fd.exec():
print('string list {}'.format(fd.selectedFiles()))
The only drawback of this approach is that the fileSelected and filesSelected signals are not sent.
Musicamante's answer was very, very helpful, in particular showing that the selection is in fact triggered by filling the QLE with path strings.
But in fact there is a fatal flaw when the purpose is as I have stated: unfortunately, if you try to deselect the final selected file in a directory, actually this name is not then removed from the QLE. And in fact, if the QLE is set to blank this disables the "Choose" button. All this is by design: the function of a QFileDialog is either to "open" or to "save", not to "modify".
But I did find a solution, which involves finding the QListView which lists the files in the directory, and then using a signal on its selection model.
Another thing this caters for is what happens when you change directory: obviously, you then want the selection to be updated on the basis of the project's files as found (or not found) inside that directory. I've in fact changed the text of the "choose" button to show that "modification" is the name of the game.
fd = QtWidgets.QFileDialog(app.get_main_window(), 'Modify project files', start_directory, '(*.docx)')
fd.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
fd.setViewMode(QtWidgets.QFileDialog.List)
fd.setLabelText(QtWidgets.QFileDialog.Reject, '&Cancel')
fd.setLabelText(QtWidgets.QFileDialog.Accept, '&Modify')
fd.setOptions(options)
file_name_line_edit = fd.findChild(QtWidgets.QLineEdit, 'fileNameEdit')
list_view = fd.findChild(QtWidgets.QListView, 'listView')
# utility to cope with all permutations of backslashes and forward slashes in path strings:
def split_file_path_str(path_str):
dir_path_str, filename = ntpath.split(path_str)
return dir_path_str, (filename or ntpath.basename(dir_path_str))
fd.displayed_dir = None
sel_model = list_view.selectionModel()
def sel_changed():
if not fd.displayed_dir:
return
selected_file_paths_in_shown_dir = []
sel_col_0s = sel_model.selectedRows()
for sel_col_0 in sel_col_0s:
file_path_str = os.path.join(fd.displayed_dir, sel_col_0.data())
selected_file_paths_in_shown_dir.append(file_path_str)
already_included = file_path_str in self.files_list
if not already_included:
fd.project_files_in_shown_dir.append(file_path_str)
# now find if there are any project files which are now NOT selected
for project_file_path_str in fd.project_files_in_shown_dir:
if project_file_path_str not in selected_file_paths_in_shown_dir:
fd.project_files_in_shown_dir.remove(project_file_path_str)
sel_model.selectionChanged.connect(sel_changed)
def file_dlg_dir_entered(displayed_dir):
displayed_dir = os.path.normpath(displayed_dir)
# this is set to None to prevent unwanted selection processing triggered by setText(...) below
fd.displayed_dir = None
fd.project_files_in_shown_dir = []
existing = []
for file_path_str in self.files_list:
dir_path_str, filename = split_file_path_str(file_path_str)
if dir_path_str == displayed_dir:
existing.append(f'"{file_path_str}"')
fd.project_files_in_shown_dir.append(file_path_str)
file_name_line_edit.setText(' '.join(existing))
fd.displayed_dir = displayed_dir
fd.directoryEntered.connect(file_dlg_dir_entered)
# set the initially displayed directory...
file_dlg_dir_entered(start_directory)
if fd.exec():
# for each file, if not present in self.files_list, add to files list and make self dirty
for project_file_in_shown_dir in fd.project_files_in_shown_dir:
if project_file_in_shown_dir not in self.files_list:
self.files_list.append(project_file_in_shown_dir)
# also add to list widget...
app.get_main_window().ui.files_list.addItem(project_file_in_shown_dir)
if not self.is_dirty():
self.toggle_dirty()
# but we also have to make sure that a file has not been UNselected...
docx_files_in_start_dir = [f for f in os.listdir(fd.displayed_dir) if os.path.isfile(os.path.join(fd.displayed_dir, f)) and os.path.splitext(f)[1] == '.docx' ]
for docx_file_in_start_dir in docx_files_in_start_dir:
docx_file_path_str = os.path.join(fd.displayed_dir, docx_file_in_start_dir)
if docx_file_path_str in self.files_list and docx_file_path_str not in fd.project_files_in_shown_dir:
self.files_list.remove(docx_file_path_str)
list_widget = app.get_main_window().ui.files_list
item_for_removal = list_widget.findItems(docx_file_path_str, QtCore.Qt.MatchExactly)[0]
list_widget.takeItem(list_widget.row(item_for_removal))
if not self.is_dirty():
self.toggle_dirty()
After a few hours of playing around, here's a pretty good way to programatically pre-select multiple files in a QFileDialog:
from pathlib import Path
from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QFileDialog, QListView
p_files = Path('/path/to/your/files')
dlg = QFileDialog(
directory=str(p_files),
options=QFileDialog.DontUseNativeDialog)
# get QListView which controls item selection
file_view = dlg.findChild(QListView, 'listView')
# filter files which we want to select based on any condition (eg only .txt files)
# anything will work here as long as you get a list of Path objects or just str filepaths
sel_files = [p for p in p_files.iterdir() if p.suffix == '.txt']
# get selection model (QItemSelectionModel)
sel_model = file_view.selectionModel()
for p in sel_files:
# get idx (QModelIndex) from model() (QFileSystemModel) using str of Path obj
idx = sel_model.model().index(str(p))
# set the active selection using each QModelIndex
# IMPORTANT - need to include the selection type
# see dir(QItemSelectionModel) for all options
sel_model.select(idx, QItemSelectionModel.Select | QItemSelectionModel.Rows)
dlg.exec_()
dlg.selectedFiles()
>>> ['list.txt', 'of.txt', 'selected.txt', 'files.txt']

Python search engine GUI(PySimpleGui) - Listbox with right_click_menu

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()

Text not updating in PySimpleGui

In my code, Im trying to make a calculator. So there is a 1 button which when pressed, updates the Question: text by adding 1 to it's text. So when I press 1, the text will convert from Question: to Question: 1. But its not updating. I have faced this problem before too. I think when I do the .update, it will only update the value till its the same number of letters as the text already has. If it has 2 letters and I try to .update('123'), it will only update to 12. Is there any way to get around this???
import PySimpleGUI as sg
layout = [
[sg.Text('Question: ', key='-IN-')],
[sg.Text('Answer will be shown here', key='-OUT-')],
[sg.Button('1'), sg.Button('2'), sg.Button('3')],
[sg.Button('4'), sg.Button('5'), sg.Button('6')],
[sg.Button('7'), sg.Button('8'), sg.Button('9')],
[sg.Button('Enter'), sg.Button('Exit')]
]
window = sg.Window('calculator', layout)
while True:
event, values = window.read()
if event is None or event == 'Exit':
break
elif event == '1':
bleh = window['-IN-'].get()
teh = f'{bleh}1'
window['-IN-'].update(value=teh)
window.close()
As above comment, Example like this,
import PySimpleGUI as sg
layout = [
[sg.InputText('Question: ', readonly=True, key='-IN-')],
[sg.Text('Answer will be shown here', key='-OUT-')],
[sg.Button('1'), sg.Button('2'), sg.Button('3')],
[sg.Button('4'), sg.Button('5'), sg.Button('6')],
[sg.Button('7'), sg.Button('8'), sg.Button('9')],
[sg.Button('Enter'), sg.Button('Exit')]
]
window = sg.Window('calculator', layout)
input = window['-IN-']
while True:
event, values = window.read()
if event is None or event == 'Exit':
break
elif event in '1234567890':
bleh = window['-IN-'].get()
teh = f'{bleh}{event}'
input.update(value=teh)
input.Widget.xview("end") # view end if text is too long to fit element
window.close()

PySimpleGUI get selected extension from FileSaveAs dialog box

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

Categories

Resources