tkinter mainloop – is there any way to use button or checkbutton without mainloop?

Question:

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.

Asked By: user

||

Answers:

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.

Answered By: Scott Paterson
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.