Use variable outside of function or in other function? - python

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!

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.

Python call function defined in class

So I am making a game and I am using Tkinter and Python 2.7, and I want to store map data in files. I am trying to make a menu to open the file and set to a variable. I am having trouble defining a function to use on the Tkinter Button.
The Tkinter window opens but when I click the button it gives an error.
Code:
#readfiles.py
class maps(object):
def __init__(self):
self.data
def mapset(var):
data = var
fselect = Tk()
...
buttons = Frame(fselect).pack()
Label(fselect, text="Select maps in folder:")
for i in listdir('./maps/'):
if i[-4:] == ".pvm":
Button(buttons, text=i[:-4], command=lambda i=i: mapset(open('./maps/'+i, 'r').read())).pack()
NameError: global name 'mapset' is not defined
Also, how can I access the data variable in maps (so it would be maps.data, right) in another file?
#main.py
from readfiles import *
print maps.data
AttributeError: type object 'maps' has no attribute 'data'
Let me know if you need any additional information.
Also, print "Happy New Years!
Here's a simple GUI class that stores the data from a map file in the self.data attribute.
To test this code I created a set of simple text files using this Bash command:
for i in {0..5}; do echo >"maps/map${i}.pvm" "Map $i data"; done
And here's the Python 3 code. To run it on Python 2, just change the Tkinter import statement to import Tkinter as tk, and add from __future__ import print_function before the other import statements.
I've added a "Print data" that lets us test that we have actually loaded the data correctly.
import tkinter as tk
import os
class Gui(object):
def __init__(self, mapspath):
self.data = None
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
tk.Label(frame, text="Select maps in folder:").pack()
for fbase in os.listdir(mapspath):
if fbase.endswith('.pvm'):
fname = os.path.join(mapspath, fbase)
#print(fname)
tk.Button(frame, text=fbase[:-4],
command=lambda fn=fname:self.read_map(fn)).pack()
tk.Button(frame, text="Print data", command=self.print_map).pack()
root.mainloop()
def read_map(self, fname):
with open(fname) as f:
self.data = f.read()
def print_map(self):
print("current map:", self.data)
Gui("./maps")
Unless these maps are huge it would probably be more convenient to store all of them at once. You could store them in a list, but it'd probably be better to store them in a dictionary, using the base name (minus the '.pvm' extension) as the key.

Why doesn't save() write to file first time?

I am making an text editor and I am working on the save/save-as buttons and have found something that I first thought worked but then found a problem with. The save and save as buttons in the menu don't save the text in the text widget to the file but just creates one. I have put in both functions self.f1.write(text) but only after a few clicks does the text actually save. It is not a time thing because I waited about five minutes and it still didn't work. I have a Mac on Yosemite.
Why doesn't it work?
Here is the script:
#modules
from Tkinter import *
from Tkinter import TclError
import tkFont
import tkMessageBox
import tkFileDialog
class Main(object):
def __init__(self, root):
root.title("PyText")
#menu for the file cascade
self.m1=Menu(root)
self.appmenu = Menu(self.m1, name="apple", tearoff=0)
self.m1.add_cascade(menu=self.appmenu)
self.appmenu.add_command(label="About PyText")
self.appmenu.add_separator()
self.fm=Menu(self.m1, tearoff=0)
self.fm.add_command(label="New", command=self.saveas)
self.fm.add_command(label="Open", accelerator="Cmd+O", command=self.open)
#these two don't work first time...
self.fm.add_command(label="Save", accelerator="Cmd+S", command=self.save)
self.fm.add_command(label="Save As", command=self.saveas)
self.fm.add_separator()
self.fm.add_command(label="Exit", command=root.quit)
self.m1.add_cascade(label="File", menu=self.fm)
root.config(menu=self.m1)
#Main text widget
self.t1=Text(root)
self.t1.config(width=90, height=40, undo=True, highlightbackground="black", cursor="ibeam")
self.t1.grid(row=1)
# Here is the problem.
# this command creates the file but does not
# save the text to the file.
def saveas(self):
text = self.t1.get(0.0, END)
self.savelocation=tkFileDialog.asksaveasfilename()
self.file=open(self.savelocation, "w+")
self.file.write(text)
# this also has
# the same problem. Once save as has
# been called, it does not save when pressed
# in first click but after a few clicks it
# finaly saves.
def save(self):
try:
text = self.t1.get(0.0, END)
self.f1=open(self.file, "w+")
self.f1.write(text)
except IOError:
text = self.t1.get(0.0, END)
self.f1=open(self.savelocation, "w+")
self.f1.write(text)
except Exception:
tkMessageBox.showinfo("Error", "Please save-as first.")
raise
#works fine!
def open(self):
self.file=tkFileDialog.askopenfilename()
self.OpenFile=file(self.file) # get a file handle
self.ReadFile= self.OpenFile.read() # read the file to variable
self.OpenFile.close() # close file handle
self.t1.delete(0.0, END)
self.t1.insert(END, self.ReadFile)
root = Tk()
app = Main(root)
root.mainloop()
Your code is saving data. However, you aren't closing the file and python buffers output, so the file on disk may not actually have any data until something causes the file to be closed (such as exiting the program normally).
The simple solution is to make sure you close the file after writing to it, and the easiest way to do that is by using open with the with statement.
def save(self):
try:
with open(self.file, "w+") as f1:
texts = self.t1.get("1.0", "end-1c")
f1.write(text)
...
In the above code, when the with block finishes, the file is guaranteed to be closed and the context flushed to disk.
Note: your use of the index 0.0 is incorrect. The index must be a string, and the first character is "1.0" not "0.0". Also, Tkinter always adds an extra newline to the text widget. If you want exactly what the user entered you need to get all but the very last character, which is what "end-1c" means (end, minus one character).

How can I make this 2 functions work in python?

I am learning Python. Why do I get the error Exception in Tkinter callback?
I'm trying to Browse the file from the button "Browse" and zip the file in "Compress" button. I don't know what is wrong with my code? Please help!
from Tkinter import *
import tkFileDialog
import gzip
class SecureBox(Frame):
def browse(self):
return tkFileDialog.askopenfilename()
def compressFile(self):
f_in = open(self.browse, 'rb')
f_out = gzip.open('compressFile.gz', 'wb')
f_out.writelines(f_in)
f_out.close()
f_in.close()
def createWidgets(self):
# Pick a file from user
self.compress = Button(self)
self.compress["text"] = "1. Browse"
self.compress["command"] = self.browse
self.compress["fg"] = "blue"
self.compress.pack({"side": "top"})
# Pick a file from user and compress the file
self.compress = Button(self)
self.compress["text"] = "2. Compress"
self.compress["command"] = self.compressFile
self.compress["fg"] = "blue"
self.compress.pack({"side": "top"})
# Adding this function to get the widget show up
def __init__(self, master):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
root = Tk()
root.title ("Secure Box")
root.geometry("500x350")
sb = SecureBox(root)
# start the program
sb.mainloop()
root.destroy()
Save the filename as a class attribute, and use that to open the file:
def browse(self):
self.filename = tkFileDialog.askopenfilename()
def compressFile(self):
f_in = open(self.filename, 'rb')
...
Explanation:
Basically, when you're doing open(self.browse, 'rb') you're passing a function reference to open instead of a filename. This won't work, since open cannot open a function, it can open files. You could use open(self.browse(), 'rb') since self.browse() returns a filename, but that would render your Browse button useless, since the file dialog would then be opened when you click the Compress button.
When saving the filename as a class attribute and using that to open the file as I propose above, you also don't need the return in the browse function. In Tkinter, a button callback is called without any arguments and no return arguments are saved, so returning anything doesn't do anything. If you choose to do remove the browse button altogether and use the open(self.browse(), 'rb') method, you do need to return the filename (but it doesn't need to be a class attribute).

Python's tkinter Buttons - How to get return values of functions from other buttons?

I am working on a tkinter interface for an assignment that opens a file, reads and modifies it, then outputs it to a new file. I have buttons that, when clicked, let the user browse their computer for the appropriate files. Those separate functions return the filename ("randomInputFile.txt").
My problem is that I have a third button that should take those two values and then use them in the reading/writing process. I'm not sure how to pass the input/output filenames as parameters to the read/write function.
Should I just pass the filenames as global variables within the respective function?
from tkinter.filedialog import askopenfilename
from tkinter.filedialog import asksaveasfilename
def openGUI():
wn = Tk()
wn.title("Homework 10 - CSIS 153")
openFileButton = Button(wn, text="Open", command = openFile)
openFileButton.pack()
openFileButton.place(bordermode=OUTSIDE, height=90, width=90)
saveFileButton = Button(wn, text="Save to...", command = saveFile)
saveFileButton.pack()
saveFileButton.place(bordermode=OUTSIDE, height=90, width=90, x=110, )
executeButton = Button(wn, text="Run Program", command = splitSentences(****SOMETHING, SOMETHING****))
executeButton.pack()
executeButton.place(bordermode=OUTSIDE, height=90, width=123, x=40, y=115)
wn.mainloop()
def openFile():
inputFile = askopenfilename()
msg = "You are opening:\n\n" + str(inputFile)
messagebox.showinfo("File Location", msg)
return inputFile
def saveFile():
outputFile = asksaveasfilename()
return outputFile
def splitSentences(inFile, outFile):
with open(inFile) as myFile:
#etc etc
You can't return anything to a Button, so there's no use in those lines at the end of the functions. And yes, the easiest thing to do would be to make inputFile and outputFile global variables. Then, you also wouldn't need to pass them as an argument to splitSentences(), that function would just access them directly.
However, the better way to do it would be to make your GUI a class, and those variables as instance variables. You should also provide some way to disable the executeButton until you have values for the inputFile and outputFile variables, otherwise that function will throw an error.

Categories

Resources