Run a function on button command using tkinter - python

first of all, I'm a beginner. I have a basic script (get grades()) that takes a .csv file and specific class sections (min 1, max 3). I wrote this function to clean courses grades, and the output is a .txt containing only the student number, class section and grade. This text file is then ready to be upload to a system at my uni.
I'm trying to create a GUI for this script, but I'm stuck. Googling around, I managed to get a basic GUI, but even though I can enter the class sections and browse for a file, I cannot make it run my function. When I click on the button to the get the grades, the GUI crashes. Could you please point me in the right direction? Thank you in advance.
tkinter code
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo
import tkinter
from tkinter import *
from tkinter import filedialog as fd
from get_grades import get_grades
from functools import partial
# root window
root = tk.Tk()
root.geometry("500x450")
root.title('Grades')
# store sections
sections = tk.StringVar()
file_name = tk.StringVar()
def save_sections():
""" callback when the sections button clicked
"""
msg = f'You entered sections: {sections.get()}'
showinfo(
title='Information',
message=msg
)
# Sign in frame
signin = ttk.Frame(root)
signin.pack(padx=10, pady=10, fill='x', expand=True)
# sections
sections_label = ttk.Label(signin, text="sections:")
sections_label.pack(fill='x', expand=True)
sections_entry = ttk.Entry(signin, textvariable=sections)
sections_entry.pack(fill='x', expand=True)
sections_entry.focus()
# login button
section_button = ttk.Button(signin, text="save sections", command=save_sections)
section_button.pack(fill='x', expand=True, pady=10)
def get_file_name(file_entry):
file_name = fd.askopenfilename(title="Select file", filetypes=(("CSV Files", "*.csv"),))
file_entry.delete(0, END)
file_entry.insert(0, file_name)
entry_csv = Entry(root, text="", width=50)
entry_csv.pack(fill='x', expand=True)
file_label = ttk.Label(root, text="Input CSV")
file_button = ttk.Button(root, text="Browse...", width=10, command=lambda: get_file_name(entry_csv))
file_button.pack(fill='x', expand=True, pady=10)
grades_button = ttk.Button(root, text="Get grades", width=10, command=lambda: get_grades(entry_csv, sections))
grades_button.pack(fill='x', expand=True, pady=10)
# infinite loop
root.mainloop()
get_grades.py
import pandas as pd
from datetime import datetime
def get_grades(file, section1=True, section2=False, section3=False):
sections = []
if section1:
sections.append(section1)
if section2:
sections.append(section2)
if section3:
sections.append(section3)
else:
return "missing sections"
# get file
df = pd.read_csv(file)
# delete the first two rows
df = df.drop([df.index[0], df.index[1]])
# important columns are "SIS User ID", "Section", and the name of the test, which changes by course
# first, rename the assignment column
df = df.rename(columns={df.columns[5]: "Grade"})
df = df[df.Student != "Student, Test"]
# select columns
df = df[["SIS User ID", "Section", "Grade"]]
df = df[df['Section'].isin(sections)]
# cleaning
df = df.replace("0.0", "NVD")
df = df.fillna("NA")
# deleting decimal
df['SIS User ID'] = df['SIS User ID'].astype(str).apply(lambda x: x.replace('.0', ''))
# save to txt and csv
# file name
date_time = datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")
filename = str(date_time) + "_" + str(sections)
df.to_csv(str(filename + ' .txt'), sep='\t', index=False)
print("done! file " + str(filename) + ".txt saved")

You are passing an Entry widget and a StringVar to your function. But your function expects string objects.
You have to get the values of your entry box and StringVar. Luckily, the method is called the same for both cases. It's the .get() method.
grades_button = ttk.Button(root, text="Get grades", width=10, command=lambda: get_grades(entry_csv.get(), sections.get()))
This should pass the correct values to get_grades(). But to be sure, you should print the values to console, just to check. This would also have hinted at the problem you had with your code.

Related

How to display something after an Entry widget is filled in another area of the GUI?

I'm building a GUI to write to an excel spreadsheet. However, before I do the writing to spreadsheet, the user will input data in the GUI. What I want to do is when the user puts in a release number in the first entry and "tab" to the next entry, I want the date to automatically populate.
import tkinter
from tkinter import ttk
from tkinter import messagebox
import os
import openpyxl
from datetime import datetime
window = tkinter.Tk()
window.title("Truck Log Entry Form")
frame = tkinter.Frame(window)
frame.pack()
#saving truck logs
truck_info_frame = tkinter.LabelFrame(frame, text="Truck Information")
truck_info_frame.grid(row= 0, column=0, padx=20, pady=10)
release_number_label = tkinter.Label(truck_info_frame, text="Release Number")
release_number_label.grid(row=0,column=0)
truck_name_label = tkinter.Label(truck_info_frame, text="Truck Name")
truck_name_label.grid(row=0, column=1)
tare_info_label = tkinter.Label(truck_info_frame, text="Tare Weight")
tare_info_label.grid(row=0, column=2)
date_info_label = tkinter.Label(truck_info_frame, text="Date/Time")
date_info_label.grid(row=0, column=3)
gross_info_label = tkinter.Label(truck_info_frame, text = "Gross Weight")
gross_info_label.grid(row=0, column=4)
date_info_result_str = tkinter.StringVar()
release_number_entry = tkinter.Entry(truck_info_frame, textvariable=date_info_result_str)
truck_name_entry = tkinter.Entry(truck_info_frame)
tare_info_entry = tkinter.Entry(truck_info_frame)
#date_info_result = tkinter.Label(truck_info_frame, text='')
date_info_result = tkinter.Label(truck_info_frame)
date_info_result.grid(row=1, column=3)
date_info_result_str.trace('w', lambda *args: auto_date(release_number_entry.get()) )
release_number_entry.grid(row=1, column=0)
truck_name_entry.grid(row=1, column=1)
tare_info_entry.grid(row=1, column=2)
for widget in truck_info_frame.winfo_children():
widget.grid_configure(padx=10, pady=5)
def auto_date(release_filled):
print("in auto_date function " + release_filled)
#release_filled = release_number_entry.get()
if len(release_filled) >= 6:
print("in len area")
#datetime object containing current date and time
now = datetime.now()
#format date and time to dd/mm/YY H:M:S
dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
return dt_string
else:
print("made in error")
#tkinter.messagebox.showwarning(title="Error", message="Please check release number")
window.mainloop()
I created a function for it to be called then tried to do a .trace method but that doesn't seem to work. Furthermore, I tried messing around with validate but it only returns true or false and I'm not sure how to get that and I suppose call another function to display the time under the data_info_label. I would appreciate any help on this.
Update (1/12/23): I was able to call on the function and get it to trace successfully but how do I return the date and time from the function into the label?
What I want to do is when the user puts in a release number in the first entry and "tab" to the next entry, I want the date to automatically populate.
If you want to do something when the user presses the tab key, you can bind a function to the event <Tab>. You don't want to do it on a trace associated with a stringvar since that would be called every time the user inserts or deletes a single character.
Here's a very simple example showing one way to do it:
import tkinter as tk
from datetime import datetime
def insert_date(entry):
now = datetime.now()
label.configure(text=now.strftime("%c"))
root = tk.Tk()
entry = tk.Entry(root)
label = tk.Label(root)
entry.pack(side="top")
label.pack(side="top")
entry.bind("<Tab>", lambda event: insert_date(label))
root.mainloop()
When you run the above example and press the tab key while the first entry has keyboard focus, it will insert the date and time into the second entry.
Thanks to #Brian Oakley, I was able to figure out the rest! Huge help and here's the code below to see how I did it.
import tkinter
from tkinter import ttk
from tkinter import messagebox
import os
import openpyxl
from datetime import datetime
window = tkinter.Tk()
window.title("Truck Log Entry Form")
frame = tkinter.Frame(window)
frame.pack()
#saving truck logs
truck_info_frame = tkinter.LabelFrame(frame, text="Truck Information")
truck_info_frame.grid(row= 0, column=0, padx=20, pady=10)
release_number_label = tkinter.Label(truck_info_frame, text="Release Number")
release_number_label.grid(row=0,column=0)
truck_name_label = tkinter.Label(truck_info_frame, text="Truck Name")
truck_name_label.grid(row=0, column=1)
tare_info_label = tkinter.Label(truck_info_frame, text="Tare Weight")
tare_info_label.grid(row=0, column=2)
date_info_label = tkinter.Label(truck_info_frame, text="Date/Time")
date_info_label.grid(row=0, column=3)
gross_info_label = tkinter.Label(truck_info_frame, text = "Gross Weight")
gross_info_label.grid(row=0, column=4)
#date_info_result_str = tkinter.StringVar()
#release_number_entry = tkinter.Entry(truck_info_frame, textvariable=date_info_result_str)
release_number_entry = tkinter.Entry(truck_info_frame)
truck_name_entry = tkinter.Entry(truck_info_frame)
tare_info_entry = tkinter.Entry(truck_info_frame)
#date_info_result = tkinter.Label(truck_info_frame, text='')
date_info_result = tkinter.Label(truck_info_frame)
date_info_result.grid(row=1, column=3)
#date_info_result_str.trace('w', lambda *args: auto_date(release_number_entry.get()) )
release_number_entry.grid(row=1, column=0)
truck_name_entry.grid(row=1, column=1)
tare_info_entry.grid(row=1, column=2)
release_number_entry.bind("<Tab>", lambda event: auto_date(release_number_entry.get()))
for widget in truck_info_frame.winfo_children():
widget.grid_configure(padx=10, pady=5)
def auto_date(release_filled):
print("in auto_date function " + release_filled)
#release_filled = release_number_entry.get()
if len(release_filled) >= 6:
print("in len area")
#datetime object containing current date and time
now = datetime.now()
#format date and time to dd/mm/YY H:M:S
dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
date_info_result.config(text = dt_string)
else:
print("made in error")
#tkinter.messagebox.showwarning(title="Error", message="Please check release number")
window.mainloop()

Display DataFrame in tkinter library

i want to create a simple GUI form, which asks user to browse file and then display result, i have wrote following code :
import numpy as np
import pandas as pd
from tkinter import *
from tkinter.filedialog import askopenfilename
def read_file():
filename =askopenfilename()
label1.insert(0,filename)
return
def display_file():
filename1 =label1.get()
data =pd.read_csv(filename1)
print(data.head())
root =Tk()
#root.withdraw()
label1 =Entry(root,width=100)
button =Button(root,text="Read csv file",command=read_file)
button1 =Button(root,text="Display file",command=display_file)
button.pack()
label1.pack()
button1.pack()
root.mainloop()
result is following image :
when i click read csv file, it gives me possibility to read file and result is like this :
now i need display part :
def display_file():
filename1 =label1.get()
data =pd.read_csv(filename1)
print(data.head())
this function just display files in working directory, but i need to show(5 rows of dataframe) in GUI form, let us suppose that file contains just two column - please tell me how to do? i have searched a lot but could not find exact solution (different solutions was presented and i was confused)
You have to add a Tkinter element where you want the data to be displayed (here, I choose Label) using a Variable element and set this element to hold your data from inside the display_file formula:
import numpy as np
import pandas as pd
from tkinter import *
from tkinter.filedialog import askopenfilename
def read_file():
filename = askopenfilename()
label1.insert(0, filename)
return
def display_file():
filename1 = label1.get()
data = pd.read_csv(filename1)
print(data.head())
pd_variable.set(data.head())
root = Tk()
# root.withdraw()
label1 = Entry(root, width=100)
button = Button(root, text="Read csv file", command=read_file)
button1 = Button(root, text="Display file", command=display_file)
pd_variable = Variable(root)
label2 = Label(root, textvariable=pd_variable)
button.pack()
label1.pack()
button1.pack()
label2.pack()
root.mainloop()

Update Label in Tkinter to prevent overlapping text

I'm new to Tkinter and I'm trying to create a simple Button that opens a file dialog box, where the user chooses which CSV file to open. Under the button there is a label that should display the file path for the file that was opened.
When I click on the button once, everything works as expected. However, if I click on it a second time and select a different file, the new filepath overlaps with the previous one, instead of replacing it.
Here is the code for the implementation function (please let me know if you need more bits of code for context):
def open_csv_file():
global df
global filename
global initialdir
initialdir = r"C:\Users\stefa\Documents\final project models\Case A"
filename = filedialog.askopenfilename(initialdir=initialdir,
title='Select a file', filetypes = (("CSV files","*.csv"),("All files","*.*")))
df = pd.read_csv(os.path.join(initialdir,filename))
lbl_ok = tk.Label(tab2, text = ' ') #tab2 is a ttk.Notebook tab
lbl_ok.config(text='Opened file: ' + filename)
lbl_ok.grid(row=0,column=1)
Here is how to do it with .config(), create the label instance just once (can then grid as much as you want but probably just do that once too), then just configure the text:
from tkinter import Tk, Button, Label, filedialog
def open_file():
filename = filedialog.askopenfilename()
lbl.config(text=f'Opened file: {filename}')
root = Tk()
Button(root, text='Open File', command=open_file).grid()
lbl = Label(root)
lbl.grid()
root.mainloop()
You can use a StringVar for this. Here is an example that may help you:
from tkinter import *
from tkinter.filedialog import askopenfilename
root = Tk()
root.geometry('200x200')
def openCsv():
csvPath.set(askopenfilename())
csvPath = StringVar()
entry = Entry(root, text=csvPath)
entry.grid(column=0, row=0)
btnOpen = Button(root, text='Browse Folder', command=openCsv)
btnOpen.grid(column=1, row=0)
root.mainloop()

How to access a function dataframe variable from outside the function in Python?

In the following code I get to select an excel file using a tkinter button.
I'm using a function to read the excel file and import data and convert to dataframe.
The problem is I don't have access to the dataframe outside the function although it's a global variable, therefore I can't continue with the rest of stuff.
I can now access the df variable, but not as a DataFrame which appears to be empty.
What is best to do to get around this ?
import tkinter as tk
from tkinter import filedialog, ttk
import pandas as pd
root=tk.Tk()
root.title("THIS IS MY FIRST APPLICATION")
root.geometry("600x300")
text_import = ("Choose your file: ")
df = pd.DataFrame()
# browse file and import data
def getExcel ():
global df
import_file_path = filedialog.askopenfilename()
data1 = pd.read_excel(import_file_path)
df = pd.DataFrame(data1)
print(df)
# create button
my_button = tk.Button(root, text = text_import, width = 15, height = 2,
command = getExcel)
my_button.pack(pady=10)
my_button.place(x = 200, y = 75)
print(df.dtypes)
root.mainloop()
When print(df.dtypes) is executed, no file is loaded yet and so it is empty. You should call it inside a function which should be called after a file is loaded:
import tkinter as tk
from tkinter import filedialog, ttk
import pandas as pd
root = tk.Tk()
root.title("THIS IS MY FIRST APPLICATION")
root.geometry("600x300")
text_import = "Choose your file: "
df = None
# browse file and import data
def getExcel():
global df
import_file_path = filedialog.askopenfilename()
if import_file_path:
df = pd.read_excel(import_file_path)
print(df)
# create button
my_button = tk.Button(root, text=text_import, width=15, height=2, command=getExcel)
#my_button.pack(pady=10)
my_button.place(x=200, y=75)
def showExcel():
if df is not None:
# a file is loaded
print(df.dtypes)
tk.Button(root, text="Show File Data", width=15, height=2, command=showExcel).place(x=200, y=150)
root.mainloop()

How to Varibalize a Tkinter User Input

I am trying to create a little GUI to convert Parquet to XLSX files. In my code, I am currently using tkinter to create a GUI and then using a function to convert from Parquet to XLSX.
When I run the below code though, I am still getting an error that "myfunc() missing 3 required positional arguments: 'txt_file', 'txt_name', and 'txt_dir'" any idea why they are not getting assigned?
import os
import pandas as pd
from tkinter import *
import pyarrow
import shutil
from pathlib import Path
window = Tk()
window.title("Convertor")
#Sets the size of the window
window.geometry('550x200')
#Adds a header to the window and configures the size
lbl = Label(window, text="Convert Parquet to CSV", font=("Arial Bold", 18))
#Configures where the label message will appear
lbl.grid(column=0, row=0)
#asks for parquet file
lbl2 = Label(window, text="Where is the parquet file currently located?", font=("Arial", 12))
lbl2.grid(column=0, row=1)
#adds a field for an input text message
txt_file = Entry(window,width = 30)
txt_file.grid(column=1, row=1)
#asks for name of xlsx file
lbl3 = Label(window, text="What would you like to call the new xlsx file?", font=("Arial", 12))
lbl3.grid(column=0, row=2)
txt_name = Entry(window,width = 30)
txt_name.grid(column=1, row=2)
#asks where you want to put the new xlsx file
lbl3 = Label(window, text="Where would you like to ouput the xlsx file?", font=("Arial", 12))
lbl3.grid(column=0, row=3)
txt_dir = Entry(window,width = 30)
txt_dir.grid(column=1, row=3)
def myfunc(txt_file, txt_name, txt_dir):
file = txt_file
df1 = pd.read_parquet(file)
df = df1.append(df1, ignore_index=True)
dirout = txt_dir
name = txt_name
cfile = os.path.join(dirout, name + "." + "xlsx")
df.to_excel(cfile)
#Adding a button
btn = Button(window, text="Convert", command=myfunc)
btn.grid(column=1, row = 4)
#The mainloop causes the window to remain open until someone interacts with it
window.mainloop()
Instead of passing values into your function, have your function retrieve the values from the UI. In your case you're saving the widgets in global variables which makes that easy to do.
For example:
def my_func():
file = txt_file.get()
name = txt_name.get()
dir_out = txt_dir.get()
df1 = pd.read_parquet(file)
df = df1.append(df1, ignore_index=True)
cfile = os.path.join(dirout, name + "." + "xlsx")
df.to_excel(cfile)
...
btn = Button(window, text="Convert", command=my_func)
If you want to make your code easier to test by having the code that does the work be in a function that accepts parameters, simply move that code to a separate function.
def my_func():
file = txt_file.get()
name = txt_name.get()
dir_out = txt_dir.get()
do_conversion(file, name, dir_out)
def do_conversion(file, name, dir_out):
df1 = pd.read_parquet(file)
df = df1.append(df1, ignore_index=True)
cfile = os.path.join(dirout, name + "." + "xlsx")
df.to_excel(cfile)
With that, you can use do_conversion with or without a GUI, making it easier to write a unit test for the code that performs the actual conversion.

Categories

Resources