BWhite BWhite - 4 months ago 97
Python Question

Python 3 - Tkinter button commands

I am new to Tkinter and Python as well. I have three buttons with commands in my Tkinter frame. Button 1 calls open_csv_dialog(), opens a file dialog box to select a .csv file and returns the path. Button 2 calls save_destination_folder(), opens a file dialog box to open the preferred directory and return the path.

My problem is with Button 3. It calls modify_word_doc() which needs the filepaths returned from button 1 and button 2.

I have tried;

button3 = ttk.Button(root, text="Run", command=lambda: modify_word_doc(open_csv_dialog, save_destination_folder)).pack()


but that obviously just prompts the file dialog box to open again for both the open_csv_dialog() and save_destination_folder() function which is undesired. I would like to just use the file path that was already returned from these two functions and pass it into modify_word_doc without being prompted by another file dialog box. I have also tried to use
partial
but I'm either using it wrong or it still has the same undesired consequences.

I have read the Tkinter docs about commands and searched SO for a possible answer, so apologies if this has been answered before and I failed to find it.

import tkinter as tk
from tkinter import filedialog
from tkinter import ttk
import os
import csv
import docx
from functools import partial


root = tk.Tk()


def open_csv_dialog():
file_path = filedialog.askopenfilename(filetypes=(("Database files",
"*.csv"),("All files", "*.*")))
return file_path


def save_destination_folder():
file_path = filedialog.askdirectory()
return file_path


def modify_word_doc(data, location):
#data = open_csv_dialog()
#location = save_destination_folder()
#long code. takes .csv file path opens, reads and modifies word doc with
#the contents of the .csv, then saves the new word doc to the requested
#file path returned from save_destination_folder().


label = ttk.Label(root, text="Step 1 - Choose CSV File.",
font=LARGE_FONT)
label.pack(pady=10, padx=10)
button = ttk.Button(root, text="Choose CSV",
command= open_csv_dialog).pack()
label = ttk.Label(root,
text="Step 2 - Choose destination folder for your letters.",
font=LARGE_FONT)
label.pack(pady=10, padx=10)
button2 = ttk.Button(root, text="Choose Folder",
command=save_destination_folder).pack()
label = ttk.Label(root, text="Step 3 - Select Run.", font=LARGE_FONT)
label.pack(pady=10, padx=10)
button3 = ttk.Button(root, text="Run",
command=lambda: modify_word_doc(open_csv_dialog, save_destination_folder)).pack()


root.mainloop()

Answer

This was probably just an error typing the question.... but for completeness on this line

button3 = ttk.Button(root, text="Run", command=lambda: modify_word_doc(open_csv_dialog, save_destination_folder).pack()

You're missing the closing parenthesis for ttk.Button(*)*.pack()

It should be (syntactically):

 button3 = ttk.Button(root, text="Run", command=lambda: modify_word_doc(open_csv_dialog, save_destination_folder)).pack()

Also, using .pack() returns None so setting a variable to a widget + geometry manager method just sets that variable to nothing, instead of a reference to the widget object.

So, if you actually need a reference to this widget you should actually do:

 button3 = ttk.Button(*)
 button3.pack()

If you don't need a reference then just don't assign anything and save yourself some typing, since it's redundant.

For the actual question:

If I understand your question, you have two buttons which set the file path of the .csv and the destination folder. However, since both your functions use the dialog you are being prompted again even though the may have already been chosen.

You could use globals and various other ways to do this, I'll set an attribute on the base root window since i think this is easiest here...

In the below code what I did was simply set an attribute on the root window if the file_path has been selected. You can check this with an if statement.

Then on either I call check_state to see if the root window has both of these attributes getattr(object, string, default) will return the attribute or the default if the attribute does not exists. So, by setting the file_path to the string, or None if the location was re-selected the state will always be updated correctly.

You can clean this up some more. You could actually make both of those one function etc if you really wanted to.

import tkinter as tk
from tkinter import filedialog, ttk

def check_state():

    if getattr(root, 'csv_path', False) and getattr(root, 'dest_path', False):
        button3['state'] = 'normal'
    else:
        button3['state'] = 'disabled'

def open_csv_dialog():

    file_path = filedialog.askopenfilename(
        filetypes=(("Database files", "*.csv"), ("All files", "*.*")))
    if file_path:
        root.csv_path = file_path
    else:
        root.csv_path = None
    check_state()

def save_destination_folder():

    file_path = filedialog.askdirectory()
    if file_path:
        root.dest_path = file_path
    else:
        root.dest_path = None
    check_state()

def modify_word_doc():
   print(root.csv_path, root.dest_path)

root = tk.Tk()
ttk.Label(root, text="Step 1 - Choose CSV File.",).pack(pady=10, padx=10)
ttk.Button(root, text="Choose CSV", command= open_csv_dialog).pack()
ttk.Label(root, text="Step 2 - Choose destination folder for your letters.").pack(pady=10, padx=10)
ttk.Button(root, text="Choose Folder", command=save_destination_folder).pack()
ttk.Label(root, text="Step 3 - Select Run.").pack(pady=10, padx=10)

#We need a reference to the widget here, for the state func...

button3 = ttk.Button(root, text="Run", state='disabled', command=modify_word_doc)
button3.pack()
root.mainloop()
Comments