Python Forum
[Tkinter] Use path from browser widget
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Use path from browser widget
#1
Hi everyone,

I am designing my first GUI app using Tkinter and have somme issues.
The purpose of the app is to read a SWMM file in order to get some data series using the specific library Swmmtoolbox.

This code works well.

Now I want to share it with my desk, thus create an .exe with a GUI.

But, I am having trouble retriveing the path of my input files from the browser widget outside the getFilepath methods.

Coul you please help me solving this issue ?


class Application(tk.Frame):

    def __init__(self):
        tk.Frame.__init__(self)

        filenameCSV = tk.StringVar()
        filenameSWMM = tk.StringVar()

        self.createWidget()

    def createWidget(self):

        ttk.Button(self, text="Sélectionner le fichier SWMM:", command=self.getFilepathSWMM).pack()
        ttk.Button(self, text="Sélectionner le fichier CSV:", command=self.getFilepathCSV).pack()
        ttk.Button(self, text="Extraire les données SWMM.", command= lambda: self.extraction(filenameSWMM, filenameCSV)).pack()
        ttk.Button(self, text="Quitter", command=self.quit).pack()

    def getFilepathSWMM(self):
        self.filenameSWMM = filedialog.askopenfilename(initialdir =  "/", title = "Sélectionner le fichier", filetype = [('SWMM File', '*.out')])
        self.label = ttk.Label(self, text = "")
        self.label.pack()
        self.label.configure(text = self.filename)

    def getFilepathCSV(self):
        self.filenameCSV = filedialog.askopenfilename(initialdir =  "/", title = "Sélectionner le fichier", filetype = [('CSV File', '*.csv')])
        self.label = ttk.Label(self, text = "")
        self.label.pack()
        self.label.configure(text = self.filename)

    def quit(self):
        self.destroy()

    def extraction(path1,path2):

        book = Workbook()
        feuill1 = book.active
        file = open(path2)
        data = csv.reader(file, delimiter=';')
        array = list(data)
        for i in range(1, len(array)+1):
            extracti =  swmmtoolbox.extract(path1, [array[i-1][0], array[i-1][1], array[i-1][2]])
            extracti = extracti.reset_index()
            feuill1.cell(1,column=3*i).value = array[i-1][1]
            feuill1.cell(1,column=3*i+1).value = array[i-1][1]
            feuill1.column_dimensions[get_column_letter(3*i+1)].width = 20
            feuill1.column_dimensions[get_column_letter(3*i)].width = 20
            for j in range(2,extracti.shape[0]):
                feuill1.cell(j,column=3*i).value = extracti.iat[j,0]
                feuill1.cell(j,column=3*i+1).value = extracti.iat[j,1]
        book.save("Extraction_SWMM_2.xlsx")

if __name__ == "__main__":
    app = Application()
    app.title("SWMM Extraction")
    app.geometry("200x200")
    app.mainloop()
Reply
#2
You could treat it as a file dialog and just return the selected filename.
    def getFilepathCSV(self):
        return filedialog.askopenfilename(initialdir =  "/", title = "Sélectionner le fichier", filetype = [('CSV File', '*.csv')])
or you could wrap it in a function that uses the filename,
    def openFilepathCSV(self):
        filename = filedialog.askopenfilename(initialdir =  "/", title = "Sélectionner le fichier", filetype = [('CSV File', '*.csv')]))
        with open(filename, "r") as file:
            call_some_func(file)
Either way your GUI will need a way to call code that draws the browser and processes the selected filename. This could be a button, or a menu.
Reply
#3
You save it as self.filenameSWMM. What is the problem. Where do you want to use it, and is it in this program or another?
Reply
#4
(Aug-22-2022, 07:22 PM)woooee Wrote: You save it as self.filenameSWMM. What is the problem. Where do you want to use it, and is it in this program or another?

That's the problem. I want to use the path save as self.filenameSWMM in the method extraction as path2.
Reply
#5
filenameCSV and filenameSWMM should both be instance variables so they can be seen outside the __init__() method. You don't use these as StringVars and they are reassigned in getFilepath***, so may as well initialize them to None.
class Application(tk.Frame):
 
    def __init__(self):
        tk.Frame.__init__(self)
 
        self.filenameCSV = None
        self.filenameSWMM = None
 
        self.createWidget()
If you want getFilepathCSV to be a static method you should use a static decorator. When calling the function you need to use self.filenameSWMM and self.filenameCSV because there are no filenameSWMM or filenameCSV variables.
    def createWidget(self):
 
        ttk.Button(self, text="Sélectionner le fichier SWMM:", command=self.getFilepathSWMM).pack()
        ttk.Button(self, text="Sélectionner le fichier CSV:", command=self.getFilepathCSV).pack()
        ttk.Button(self, text="Extraire les données SWMM.",
            command= lambda: self.extraction(self.filenameSWMM, self.filenameCSV)).pack()
        ttk.Button(self, text="Quitter", command=self.quit).pack()

    @staticmethod
    def extraction(path1,path2):
        book = Workbook()
        feuill1 = book.active
        file = open(path2)
        data = csv.reader(file, delimiter=';')
        array = list(data)
        for i in range(1, len(array)+1):
            extracti =  swmmtoolbox.extract(path1, [array[i-1][0], array[i-1][1], array[i-1][2]])
            extracti = extracti.reset_index()
            feuill1.cell(1,column=3*i).value = array[i-1][1]
            feuill1.cell(1,column=3*i+1).value = array[i-1][1]
            feuill1.column_dimensions[get_column_letter(3*i+1)].width = 20
            feuill1.column_dimensions[get_column_letter(3*i)].width = 20
            for j in range(2,extracti.shape[0]):
                feuill1.cell(j,column=3*i).value = extracti.iat[j,0]
                feuill1.cell(j,column=3*i+1).value = extracti.iat[j,1]
        book.save("Extraction_SWMM_2.xlsx")
 
Bettter yet, make extraction an instance method. Now you don't need the lambda.
    def createWidget(self):
 
        ttk.Button(self, text="Sélectionner le fichier SWMM:", command=self.getFilepathSWMM).pack()
        ttk.Button(self, text="Sélectionner le fichier CSV:", command=self.getFilepathCSV).pack()
        ttk.Button(self, text="Extraire les données SWMM.", command= self.extraction).pack()
        ttk.Button(self, text="Quitter", command=self.quit).pack()

def extraction(self):
 
        book = Workbook()
        feuill1 = book.active
        file = open(self.filenameCSV)
        data = csv.reader(file, delimiter=';')
        array = list(data)
        for i in range(1, len(array)+1):
            extracti =  swmmtoolbox.extract(self.filenameSWMM, [array[i-1][0], array[i-1][1], array[i-1][2]])
            extracti = extracti.reset_index()
            feuill1.cell(1,column=3*i).value = array[i-1][1]
            feuill1.cell(1,column=3*i+1).value = array[i-1][1]
            feuill1.column_dimensions[get_column_letter(3*i+1)].width = 20
            feuill1.column_dimensions[get_column_letter(3*i)].width = 20
            for j in range(2,extracti.shape[0]):
                feuill1.cell(j,column=3*i).value = extracti.iat[j,0]
                feuill1.cell(j,column=3*i+1).value = extracti.iat[j,1]
        book.save("Extraction_SWMM_2.xlsx")
You could make.filenameCSV and filenameSWMM StringVars and use them to update the labels in your program. Note that StringVars use get() and set() to get the value or set the value of the variable. This is your code reimagined (minus the extract method since I don't care to install the package it uses).
import tkinter as tk
from tkinter import filedialog
from tkinter import ttk

class Application(tk.Tk):
 
    def __init__(self, *args, title=None, **kwargs):
        super().__init__()
        if title:
            self.title(title)
        self.csv_file = tk.StringVar()
        tk.Label(self, text="CSV", width=5).grid(row=0, column=0, padx=5, pady=5)
        tk.Label(self, textvariable=self.csv_file, width=64).grid(row=0, column=1)
        ttk.Button(self, text="Select", command=self.get_csv).grid(row=0, column=2, padx=5, pady=5)

        self.swmm_file = tk.StringVar()
        tk.Label(self, text="SWMM", width=5).grid(row=1, column=0, padx=5, pady=5)
        tk.Label(self, textvariable=self.swmm_file, width=64).grid(row=1, column=1)
        ttk.Button(self, text="Select", command=self.get_swwm).grid(row=1, column=2, padx=5, pady=5)

        ttk.Button(self, text="Extraire les données SWMM.", command= self.extraction) \
            .grid(row=2, column=1)
        ttk.Button(self, text="Quitter", command=self.quit).grid(row=2, column=2, padx=5, pady=5)
 
    def get_swwm(self):
        self.swmm_file.set(
            filedialog.askopenfilename(
                initialdir =  "/",
                title = "Sélectionner le fichier",
                filetype = [('SWMM File', '*.out')]))
 
    def get_csv(self):
        self.csv_file.set(
            filedialog.askopenfilename(initialdir =  "/",
            title = "Sélectionner le fichier",
            filetype = [('CSV File', '*.csv')]))
 
    def quit(self):
        self.destroy()
 
    def extraction(self):
        print(f"Extracting {self.csv_file.get()} to make {self.swmm_file.get()}")
 
if __name__ == "__main__":
    Application(title="SWMM Extraction").mainloop()
Reply
#6
Make extraction() a member of the class
def extraction(self):
    print(self.filenameSWMM)
Reply
#7
extraction is a "method" of the class just by being indented at the same level as all the other methods. "self" has nothing to do with it being method. "self" is a naming convention for the first argument that is passed to any instance method of a class.

There are many problems with this:
    def extraction(path1,path2):
1. path1 is where we expect to see "self". The path1 variable is going to reference an Application object, not a filename.
2. path2 is going to get filenameSWMM but the programmer expects it to get filenameCSV
3. There is no argument to receive filenameSWMM

If you rewrite so the first argument is self, you fix those problems, but get new problems.
    def extraction(self, path1, path2):
This straightens out the arguments passed to extraction, but you still have the problem that neither filenameSWMM nor filenameCSV are defined when this is executed:
        ttk.Button(self, text="Extraire les données SWMM.", command= lambda: self.extraction(filenameSWMM, filenameCSV)).pack()
That can be fixed by making filenameSWMM and filenameCSV instance variables of Application.
class Application(tk.Frame):
    def __init__(self):
        tk.Frame.__init__(self)
        self.filenameCSV = self.filenameSWMM = None
        self.createWidget()
Now that filenameCSV and filenameSWMM are instance variables it doesn't make sense to use a lambda expression when binding the extraction method. Now the button that calls extraction should be made like this:
ttk.Button(self, text="Extraire les données SWMM.", command=self.extraction).pack()
And the extraction method should look like this:
        book = Workbook()
        feuill1 = book.active
        file = open(self.filenameCSV)  # Use the instance variables
        data = csv.reader(file, delimiter=';')
        array = list(data)
        for i in range(1, len(array)+1):
            extracti =  swmmtoolbox.extract(self.filenameSWMM, [array[i-1][0], array[i-1][1], array[i-1][2]])   # Use the instance variables
            extracti = extracti.reset_index()
            feuill1.cell(1,column=3*i).value = array[i-1][1]
            feuill1.cell(1,column=3*i+1).value = array[i-1][1]
            feuill1.column_dimensions[get_column_letter(3*i+1)].width = 20
            feuill1.column_dimensions[get_column_letter(3*i)].width = 20
            for j in range(2,extracti.shape[0]):
                feuill1.cell(j,column=3*i).value = extracti.iat[j,0]
                feuill1.cell(j,column=3*i+1).value = extracti.iat[j,1]
        book.save("Extraction_SWMM_2.xlsx")
Or at least something like that, No doubt the function contains errors since it hasn't been run yet.

Another option is to make extraction a static method. static methods are just functions that are associated with a class. They are not automatically passed an instance or a class as the fist argument. I mentioned this in my earlier post. But I agree wooee that extraction should really be an instance method and have "self" as the first (and I think the only) argument.

And finally we have the problem that your program never calls tkinter.Tk() Tk() does more than make a window. It also initializes tkinter.
Reply
#8
Thanks a lot for your answers guys.

I definitely have a better understanding of instance variable and methods now.
I'll try to rewrite my code using your suggestions in order to have a "proper" script.

Thanks again!!
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020