Tkinter file dialog combining save and load dialogs - python

I have an entry widget where the user can type in a file location, and underneath that a "save" button and a "load" button. Depending on which button is clicked, the file specified in the entry widget is either opened for writing, or for reading.
This all works fine and dandy.
Now I want to add a "browse" button, which the user can click to open a file dialog to select a file. When a file is selected, the filename is copied into the entry. From there on, the save and load buttons should work fine.
However, I can't figure out how to get the file dialog to work for both reading a file and writing. I can't use tkFileDialog.asksaveasfilename because that's going to complain to the user if a file already exists (which, if the user intends to "load", it should) and the tkFileDialog.askloadasfilename function doesn't let the user select a file which doesn't exist yet (which, if the user intends to "save", should be fine as well).
Is it possible to create a dialog which displays neither of these functionalities?

Is this what you're looking for:
from tkinter import *
from tkinter.filedialog import *
root = Tk()
root.title("Save and Load")
root.geometry("600x500-400+50")
def importFiles():
try:
filenames = askopenfilenames()
global file
for file in filenames:
fileList.insert(END, file)
except:
pass
def removeFiles():
try:
fileList.delete(fileList.curselection())
except:
pass
def openFile():
try:
text.delete(END)
fob = open(file, 'r')
text.insert(0.0, fob.read())
except:
pass
def saveFile():
try:
fob = open(file, 'w')
fob.write(text.get(0.0, 'end-1c'))
fob.close()
except:
pass
listFrame = Frame(root)
listFrame.pack()
sby = Scrollbar(listFrame, orient='vertical')
sby.pack(side=RIGHT, fill=Y)
fileList = Listbox(listFrame, width=100, height=5, yscrollcommand=sby.set)
fileList.pack()
sby.config(command=fileList.yview)
buttonFrame = Frame(root)
buttonFrame.pack()
importButton = Button(buttonFrame, text="Import", command=importFiles)
importButton.pack(side=LEFT)
removeButton = Button(buttonFrame, text="Remove", command=removeFiles)
removeButton.pack(side=LEFT)
openButton = Button(buttonFrame, text="Open", command=openFile)
openButton.pack(side=LEFT)
saveButton = Button(buttonFrame, text="Save", command=saveFile)
saveButton.pack(side=LEFT)
text = Text(root)
text.pack()
root.mainloop()
"I want one dialog which returns a filename that can be used for both saving and loading."
You can import file names using a dialog window; remove the selected file name from the list (additional function); open the file you selected; and finally, write and save them.
P.S.: There may be some bugs in my code, but I think, the algorithm does what the question asks.

Related

Tkinter filedialog clearing previous input

I just started programming with Python and I'm trying to create a GUI using tkinter where it would ask the user to select a zip file and file destination to send it to after extracting. What I have noticed is that when a user re-enters a destination, it would still store the previous file directory as well. How do I prevent this?
import tkinter as tk
from tkinter import filedialog
# Setting the window size
screenHeight = 450
screenWidth = 350
root = tk.Tk()
# get the location of the zip file
def input_dir():
input_filedir = filedialog.askopenfilename(initialdir='/', title='Select File',
filetypes=(("zip", "*.zip"), ("all files", "*.*")))
my_label.pack()
return input_filedir
# get location of destination for file
def output_dir():
output_filename = filedialog.askdirectory()
# Setting the canvas size to insert our frames
canvas = tk.Canvas(root, height=screenHeight, width=screenWidth)
canvas.pack()
# Setting the frame size to insert all our widgets
frame = tk.Frame(root, bg='#002060')
frame.place(relwidth=1, relheight=1)
# button to get user to chose file directory
openFile = tk.Button(frame, text='Choose the file path you want to extract', command=input_dir)
openFile.pack(side='top')
# button to get destination path
saveFile = tk.Button(frame, text="Chose the location to save the file", command=output_dir)
saveFile.pack(side='bottom')
extractButton = tk.Button(frame, text="Extract Now")
root.mainloop()
I have tried adding this line of code in the def input_dir function but it changed the positioning of the buttons. I'm still working on the code for extracting the zip.
for widget in frame.winfor_children():
if isinstance(widget, tk.Label):
widget.destroy()
The files/directories the user clicks are not saved really. Your variables input_filedir and output_filename are garbage collected when their respective functions are completed. If you are talking about how the dialog boxes go back to the most recent places opened in a dialog, that is not really something you can mess with too much. The easy answer is you can add the keyword 'initialdir' when you make the dialog to just pick where it goes like this:
output_filename = filedialog.askdirectory(initialdir='C:/This/sort/of/thing/')
The long answer is that filedialog.askdirectory actually creates an instance of filedialog.Directory and in that class, it saves that information for later in a method called _fixresult (but that method also does other things that are important.) You could overwrite this by doing something like this:
class MyDirectory(filedialog.Directory):
def _fixresult(self, widget, result):
"""
this is just a copy of filedialog.Directory._fixresult without
the part that saves the directory for next time
"""
if result:
# convert Tcl path objects to strings
try:
result = result.string
except AttributeError:
# it already is a string
pass
self.directory = result # compatibility
return result
def askdirectory (**options):
"Ask for a directory, and return the file name"
return MyDirectory(**options).show()
and then using your own askdirectory function instead of filedialog.askdirectory, but that is really convoluted so I recommend using initialdir instead if you can.
P.S. When a button is clicked, it calls the function you set after "command=" when you made the button; it seems you got that. But these functions are 'void', in that their return is just ignored. That is to say, the "return input_filedir" line does nothing.

Use variable outside of function or in other function?

Ive rewritten this for more context, in a logical order this is what i want the program to do
1 by pressing open file it needs to open a specified file and put it into the text widget (done)
2 by pressing the extract file it needs to extract something from a specified subtree(in an xml file)
3 export the extracted data to a text file
but lets go back to point nr 2 as i have not yet written the extractor(easy part) first i need to refference a file i want to edit, and that is where i run into my problem. inside extract it cant acess vars in openfile and i dont want to reopen the file again.
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
import tkinter as tk
interface = tk.Tk()
interface.geometry("500x500")
interface.title("Text display")
def openfile():
filename = filedialog.askopenfilename()
print(filename)
file = open(filename)
txt = file.read()
print(txt)
T = tk.Text(interface, height=10, width=50)
T.insert(tk.END, txt)
T.grid(column=1, row=2)
return txt
def extract():
print(txt)
button = ttk.Button(interface, text="Open text File", command=openfile) # <------
button.grid(column=1, row=1)
buttonex = ttk.Button(interface, text="Extract subtitles", command=extract) # <------
buttonex.grid(column=2, row=1)
interface.mainloop()
NameError: name 'txt' is not defined (when i press extract)
As the edit of the initial questions shows that a GUI is planned I would suggest to move your TK widgets into an interface class as in the tkinter documentation. Further, if you plan to do more complex manipulations you should make an own class to hold your data resp. manipulate it.
import tkinter as tk
class App(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
# Create your buttons and connect them to the methods
self.button_load = tk.Button(self)
self.button_load["command"] = self.loadTextFromFile()
self.button_load["text"] = "Load Data"
self.button_load.gird(column=1, row=1)
self.buttonex = tk.Button(self)
self.buttonex["text"] = "Extract subtitles"
self.buttonex["command"] = self.extract()
self.buttonex.grid(column=2, row=1)
# Create the text widget
self.text_widget = tk.Text(self, height=10, width=50)
self.text_widget.grid(column=1, row=2)
def loadTextFromFile(self):
filename = filedialog.askopenfilename()
print(filename)
try:
file = open(filename)
txt = file.read()
file.close()
self.text_widget.insert(tk.END, txt)
except Exception:
# If anything went wrong, close the file before reraising
file.close()
raise
def extract(self):
# Now you have access to self.text_widget and it holds your read in text
do_something(self.text_widget)
# Maybe at the following functions to make your file importable without directly executing it. Could come in handy later on.
def run():
# create the application
myapp = App()
#
# here are method calls to the window manager class
#
myapp.master.title("My Do-Nothing Application")
myapp.master.maxsize(1000, 400)
# start the program
myapp.mainloop()
if __name__ == '__main__':
run()
Have a look at the tkinter documentation for further examples: https://docs.python.org/3/library/tkinter.html
The additional if clause checks if your module is the main module and executes the run function. This defines an entry point if you directly run your module and prevents functions from execution at import time if you intend to import the module into another module. A more detailed explanation can be found here:
What does if __name__ == "__main__": do?
---------- Old Answer --------------
As pointed out in the comment you need to somehow return your local variables back to the calling function in order to be able to use them somewhere else. This could be achieved by a simple return statement:
def openfile():
""" It is the responsibility of the caller to close the return value """
filename = filedialog.askopenfilename()
print(filename)
try:
file = open(filename)
txt = file.read()
T = tk.Text(interface, height=10, width=50)
T.insert(tk.END, txt)
T.grid(column=1, row=2)
return file
except Exception:
# If anything went wrong, close the file before reraising
file.close()
raise
would for example return the open fileid for further manipulation. E.g.:
def extract():
with openfile() as file:
txtex = file.read()
print (txtex)
If your goal is howeer, to manipulate the file content and read it later you would need to save your manipulations back into the file otherwise your second read would not see the changes. Dont forget to close your file again.
I changed three lines where I believe to have come across typos:
From: self.button_load["command"] = self.loadTextFromFile() to: self.button_load["command"] = self.loadTextFromFile
From: self.button_load.gird(column=1, row=1) to: self.button_load.grid(column=1, row=1)
From: self.buttonex["command"] = self.extract() to: self.buttonex["command"] = self.extract
Thank you for this example!

Update a label with the file's name, AFTER the file has been selected in the dile directory

Before a file is selected, the GUI will have a blank space where the file's name should be. After the file is selected, the GUI should update, and the name of the file selected should be displayed. I have made many different attempts at this, and the program does execute properly, however the name of the file is not displayed. I will show, as best I can, what the GUI should looke like before and after the file is selected
I have tried setting a StringVar() and having the label it is associated with update appropriately, however it has not worked so far. If I had to guess what was wrong, I would guess that the window ins't updating properly, but I am not sure.
import tkinter as tk
from tkinter import StringVar
from tkinter import ttk
from tkinter import filedialog
#Wraps two functions inside an object which allows both functions to use filename#
class PDFSelector:
#Allows user to select PDF to use in program#
def select_PDF(self):
#Opens file directory to select a file, and shows both folders and PDF files only#
#This should be what changes lbl1a below to the name of the file selected)
self.filename = filedialog.askopenfilename(initialdir = "/", title = "Select file", filetypes = (("pdf files", "*.pdf"), ("all files", "*.*")))
file_name.set(self.filename)
window.update_idletasks()
window.update()
#----Main----#
#Creates an instance of the wrapped functions to use the GUI#
selector = PDFSelector()
#Creats the GUI that will be used to select inputs#
window = tk.Tk()
window.geometry("600x130")
window.title("Word Frequency Program")
window.resizable(0, 0)
#Just a simple label on the GUI#
#The name of the file should appear next to "File Selected" AFTER the file has been selected by the user
lbl1 = tk.Label(window, text = "File Selected: ")
lbl1.grid(row = 1, column = 1)
file_name = StringVar()
lbl1a = tk.Label(window, textvariable = file_name)
lbl1a.grid(row = 1, column = 2)
#Calls the select_PDF method to choose a PDF for the program to read#
button1 = ttk.Button(window, text = "Select File", command = selector.select_PDF)
button1.grid(row = 1, column = 4)
window.mainloop()
window.destroy()
GUI should display the name of the file
Apparently the code does work, however there is something on my end that does not display the updated name of the PDF selected. This is for anyone who finds this question in the future, that the code I wrote works.

How to save and reload pickled data to/from a tkinter Entry box?

I am looking for a way to save the data that was typed into an Entry box on my Tkinter GUI and then immediately load that data back into the Entry box when the program is opened after it has been closed. I have done some research and found that the Pickle module is the best way to do this kind of stuff. Here is my code so far:
from tkinter import*
import pickle
root = Tk()
root.geometry("200x100")
cooltext = StringVar()
entry1 = Entry(root, textvariable=cooltext)
entry1.pack()
def save():
text = cooltext.get()
pickle.dump(text, open("savedtext.dat", "wb"))
btn = Button(root, text="save", command=save).pack()
root.mainloop()
Essentially my code just saves the data into a .dat file after the save button is clicked. How would I load the same data so if I typed "test" and closed the program and then opened it it would still say "test"?
This probably the code to load the data, but I do not know how to incorporate it here:
text = pickle.load(open("savedtext.dat", "rb"))
First, the code you provided has a syntax error. It's
text = pickle.load(open("savedtext.dat", "rb")) and not
text = pickle.load(open("savedtext.dat"), "rb"))
If you want to insert text in your Entry widget, you can use entry1.insert("end", text). It will insert the string in text at the end of what is written in your Entry widget. You can open the file, if it exists, and then, after you create your Entry widget you check if there is something to insert and then, if so, insert it.
EDIT: As #martineau said, we should avoid open a file without closing it after an interaction. You could use something like the code below to read the file content and then insert it in your widget.
with open("savedtext.dat", "rb") as file:
text = pickle.load(file)
This could be done using pickle like this:
from tkinter import *
import pickle
root = Tk()
root.geometry("200x100")
cooltext = StringVar()
entry1 = Entry(root, textvariable=cooltext)
entry1.pack()
def save():
text = cooltext.get()
with open("savedtext.dat", "wb") as pickle_file:
pickle.dump(text, pickle_file, pickle.HIGHEST_PROTOCOL)
def clear():
cooltext.set('')
def load():
with open("savedtext.dat", "rb") as pickle_file:
text = pickle.load(pickle_file)
cooltext.set(text)
Button(root, text="Save", command=save).pack()
Button(root, text="Clear", command=clear).pack()
Button(root, text="Load", command=load).pack()
root.mainloop()
I added a Clear and Load Button along with similarly named functions to make things easier to use (and test).
Note: A statement like the btn = Button(root, text="save", command=save).pack() you have in your code would result in btn being assigned the value None because the pack() doesn't return anything. That caused no harm, but only because btn wasn't ever referenced again, but in general you will need to call pack() (or grid()) in a separate statement just like you did for the Entry widget entry1.

I want to use extensions list at the time of "save as" option in File Dialog for file write in Python 3

I want to use extensions list example : [".txt",".html",".css"] for save as option in file dialog popup window. when I use this method
file_opt = options = {}
options['defaultextension'] = '.txt',I can able to write any file with .txt as default extension without choose in save as option but I want to choose extensions for save as in file dialog popup window from using my extensions list.
I'm using Python 3.5 based Anaconda IDE
If you look at the documentation here you can see that you can pass in the filetypes keyword which specifies a list of tuples that have the name, and file extension respectively for the different types of filetypes you want to be able to save as.. So you can do something along the lines of:
import tkinter as tk
from tkinter import filedialog as fd
def save_file():
filename = fd.asksaveasfilename(defaultextension='.txt',
filetypes= [('Text','.txt'), ('HTML', '.html'), ('CSS', '.css')])
if filename:
print("User saved the filename with extension:", filename.split(".")[-1])
root = tk.Tk()
button = tk.Button(root, text='Save File', command=save_file)
button.pack()
root.mainloop()
Or if you really want to use a dictionary for this:
import tkinter as tk
from tkinter import filedialog as fd
SAVE_OPTS = {'defaultextension':'.txt',
'filetypes': [('Text','.txt'), ('HTML', '.html'), ('CSS', '.css')]}
def save_file():
filename = fd.asksaveasfilename(**SAVE_OPTS)
if filename:
print("User saved the filename with extension:", filename.split(".")[-1])
root = tk.Tk()
button = tk.Button(root, text='Save File', command=save_file)
button.pack()
root.mainloop()
SaveFileDialog sd = new SaveFileDialog();
sd.Filter = "Text File (*.txt)|*.txt|PNG File (*.txt)|*.png"|...;
if(sd.ShowDialog() == DialogResult.OK) {
richTextBox1.SaveFile(sd.FileName, RichTextBoxStreamType.PlainText);
}

Categories

Resources