I am making a program that imports csv files, edits dataframes and asks user to check some rows to delete. To ask users which files to import, I am using tk.Button for file browse. For data rows to delete, I am using tk.CheckButton.
Sample code of my program looks like this:
import tkinter as tk
from tkinter import filedialog
import pandas as pd
def close():
root.quit()
root.destroy()
def file1():
global path1
path1 = filedialog.askopenfilename()
def file2():
global path2
path2 = filedialog.askopenfilename()
root = tk.Tk()
frame = tk.Frame(root).pack()
button1 = tk.Button(frame, text="Browse", command=file1).pack(side=tk.LEFT)
button2 = tk.Button(frame, text="Browse", command=file2).pack(side=tk.LEFT)
button3 = tk.Button(frame, text="Close", command=close).pack(side=tk.LEFT)
data1 = pd.read_csv(path1)
data2 = pd.read_csv(path2)
data3 = pd.concat([data1, data2])
w = tk.Label(root, text ='Program', font = "50")
w.pack()
def deleting():
data3 = data3[data3['rows']!=rows.get()]
rows = tk.StringVar()
rowCheck=tk.Checkbutton(root,
text="Rows to filter",
variable=rows,
command=deleting)
rowCheck.pack()
root.mainloop()
And of course this doesn't work because there is no main loop after Browse button, therefore neither the execution of file1, file2.
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-2-61417840f247> in <module>()
22 button3 = tk.Button(frame, text="Close", command=close).pack(side=tk.LEFT)
23
---> 24 data1 = pd.read_csv(path1)
25 data2 = pd.read_csv(path2)
26 data3 = pd.concat([data1, data2])
NameError: name 'path1' is not defined
What I want to do is to make browse buttons for each file and get absolute path to each of them so I can make dataframes. Also, I need to filter out some data according to user's choice. Finally, I guess that I can only have one mainloop inside a python script.
The problem is that I cannot get file path before mainloop and cannot get user's selection after mainloop. Somehow I should put mainloop at the end of the program - in my case after tk.CheckButton, and find a way to BROWSE and GET file path information BEFORE mainloop so that I can make dataframes.
To solve this problem, I searched about having actions after mainloop. Then I found out about Threading, but it seems like an overkill for my case. Based on my elementary understanding (so correct me if I'm wrong), threading is about distributing resources (such as memory I guess?) to many workers and managing the sequence of their actions. I don't think my program requires that much of complexity. I think what I am missing is rather a little trick. So I would appreciate any help or insight about solving this problem.
CodePudding user response:
Your trying to use path1 and path2 before you have created them.
import tkinter as tk
from tkinter import filedialog
import pandas as pd
##create all gloabl vars
path1 = None
path2 = None
data1 = None
data2 = None
data3 = None
def close():
root.quit()
root.destroy()
def file1():
global path1
path1 = filedialog.askopenfilename()
def file2():
global path2
path2 = filedialog.askopenfilename()
def deleting(): ##moved this for cleaner code
global data3
data3 = data3[data3['rows']!=rows.get()]
def load_df(path1, path2):
global path1
global path2
global data1
global data2
global data3
data1 = pd.read_csv(path1)
data2 = pd.read_csv(path2)
data3 = pd.concat([data1, data2])
root = tk.Tk()
## .pack() returns None so button1 = is not needed but splitting tk.Frame() and .pack() is needed.
frame = tk.Frame(root)
frame.pack()
tk.Button(frame, text="Browse", command=file1).pack(side=tk.LEFT)
tk.Button(frame, text="Browse", command=file2).pack(side=tk.LEFT)
tk.Button(frame, text="Close", command=close).pack(side=tk.LEFT)
w = tk.Label(root, text ='Program', font = "50")
w.pack()
rows = tk.StringVar()
rowCheck=tk.Checkbutton(root,
text="Rows to filter",
variable=rows,
command=deleting)
rowCheck.pack()
root.mainloop()
This would be a lot cleaner using a class however
import tkinter as tk
from tkinter import filedialog
import pandas as pd
class GUI:
def __init__(self): ##this is run automatically when GUI() is called
self.root = Tk()
self.path1 = None
self.path2 = None
self.data3 = None
def close(self):
self.root.quit()
self.root.destroy()
def file1(self):
self.path1 = filedialog.askopenfilename()
def file2(self):
self.path2 = filedialog.askopenfilename()
def deleting(self):
self.data3 = self.data3[self.data3['rows']!=rows.get()]
def load_df(self, path1, path2):
self.data3 = pd.concat([pd.read_csv(path1), pd.read_csv(self.path2)])
def main(self):
frame = tk.Frame(self.root)
frame.pack()
tk.Button(frame, text="Browse", command=self.file1).pack(side=tk.LEFT)
tk.Button(frame, text="Browse", command=self.file2).pack(side=tk.LEFT)
tk.Button(frame, text="Close", command=self.close).pack(side=tk.LEFT)
rows = tk.StringVar()
rowCheck=tk.Checkbutton(root, text="Rows to filter", variable=rows, command=self.deleting)
rowCheck.pack()
def run(self):
self.root.mainloop()
if __name__ == "__main__": ##if this file is being run
app = GUI()
app.main() ##creates all the buttons
app.run() ## runs the mainloop
as per your comment:
def reaload():
for child in root.winfo_children(): ## loop to delete all current widgets
child.pack_forget()
child.destroy()
rows = tk.StringVar()
rowCheck=tk.Checkbutton(root,
text="Rows to filter",
variable=rows,
command=deleting)
rowCheck.pack()
However this will also delete the close button.
if you wanted to keep the close button you would need to pass frame
into the function and then replace root.winfo_children
to frame.winfo_children()
which will delete all widgets in the frame rather from the root window.
Then just create a submit button with the command=reaload
parameter with the rest of the buttons.