I’ve build a dataframe editor that has an entry box at the top for someone to put their name in. I’ve tried several different ways to store the input into the “reviewed_by” variable, but it always returns blank, both inside and outside of the class. What could be happening here? How can I fix it?
class DataFrameEditor(ctk.CTk):
def __init__(self, dataframe, corresponding_value, selected_month):
super().__init__()
self.title("Review Invoice")
self.geometry("725x660") # Adjusted window size
self.dataframe = dataframe.copy().fillna(0) # Replace NaN with 0
self.entries = {}
self.reviewed_by = tk.StringVar()
# Add title rows to the DataFrame
self.add_title_rows_to_dataframe()
# Upper frame with logo, corresponding_value, selected_month, and reviewed_by
upper_frame = ctk.CTkFrame(self)
upper_frame.pack(fill="x", padx=10, pady=9)
# Load and display the logo after the main window is set up
script_dir = os.path.dirname(os.path.abspath(__file__))
logo_path = os.path.join(script_dir, "logo.png")
self.logo_image = Image.open(logo_path)
self.logo_image = self.logo_image.resize((220, 50), Image.LANCZOS)
self.logo_image = ImageTk.PhotoImage(master=self, image=self.logo_image)
logo_label = ctk.CTkLabel(upper_frame, image=self.logo_image, text="")
logo_label.pack(side="left", padx=10)
# Display corresponding_value and selected_month with reduced text size by 1
details_frame = ctk.CTkFrame(upper_frame)
details_frame.pack(side="left", padx=20, pady=10) # Added padding
# Program label and value on the same line
program_label = ctk.CTkLabel(details_frame, text=f"Program: ", font=("Arial bold", 11))
program_label.grid(row=0, column=0, sticky='w', padx=5)
program_value_label = ctk.CTkLabel(details_frame, text=corresponding_value, font=("Arial", 11))
program_value_label.grid(row=0, column=1, sticky='w', padx=5)
# Month label and value on the same line, below the Program line
month_label = ctk.CTkLabel(details_frame, text=f"Month: ", font=("Arial bold", 11))
month_label.grid(row=1, column=0, sticky='w', padx=5, pady=(5, 0))
month_value_label = ctk.CTkLabel(details_frame, text=selected_month, font=("Arial", 11))
month_value_label.grid(row=1, column=1, sticky='w', padx=5, pady=(5, 0))
# Reviewed by text box
reviewed_by_frame = ctk.CTkFrame(upper_frame)
reviewed_by_frame.pack(side="right", padx=10)
reviewed_by_label = ctk.CTkLabel(reviewed_by_frame, text="Reviewed by:", font=("Arial", 12))
reviewed_by_label.pack(side="top")
reviewed_by_entry = ctk.CTkEntry(reviewed_by_frame, textvariable=self.reviewed_by, width=200)
reviewed_by_entry.pack(side="top", pady=5)
self.frame = ctk.CTkFrame(self, fg_color="#302c2c")
self.frame.pack(fill="both", expand=True, padx=20, pady=20) # Center the frame within the main window
self.table = ctk.CTkScrollableFrame(self.frame, fg_color="#302c2c")
self.table.pack(fill="both", expand=True, padx=(30, 0)) # Keep the table as it was inside the frame
self.build_table()
button_frame = ctk.CTkFrame(self.frame)
button_frame.pack(fill="x", pady=10)
self.update_button = ctk.CTkButton(button_frame, text="Update", command=self.update_all_totals, fg_color="#5fa35f", width=100)
self.submit_button = ctk.CTkButton(button_frame, text="Submit", command=self.submit, width=100)
button_frame.grid_columnconfigure(0, weight=1)
button_frame.grid_columnconfigure(1, weight=1)
self.update_button.grid(row=0, column=0, padx=5, pady=5, sticky='ew')
self.submit_button.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
# Initialize reviewed_by_value to ensure it captures the input value
self.reviewed_by_value = ""
def add_title_rows_to_dataframe(self):
# Create a list of tuples where each tuple contains a title and its insertion index
title_rows = [
("Account Management", 0),
("Other Variable Fees", self.dataframe[self.dataframe['Item'] == "Documentation (additional B.U.s, brands, processes, etc.)"].index[0]),
("Warehouse Fees", self.dataframe[self.dataframe['Item'] == "Receiving minimum fee (Pallet / Sku)"].index[0]),
("Courier", self.dataframe[self.dataframe['Item'] == "Carrier Pass through Costs (Estimate, actuals will be billed) ATS ambient"].index[0])
]
# Insert titles in reverse order to maintain correct positions
for title, index in sorted(title_rows, key=lambda x: x[1], reverse=True):
title_row = pd.DataFrame({"Item": [title], "RATES": [None], "MULTIPLE": [None], "TOTAL": [None]})
self.dataframe = pd.concat([self.dataframe.iloc[:index], title_row, self.dataframe.iloc[index:]]).reset_index(drop=True)
def build_table(self):
for i, column in enumerate(self.dataframe.columns):
label = ctk.CTkLabel(self.table, text=column)
label.grid(row=0, column=i, padx=5, pady=5)
for row_index, row in self.dataframe.iterrows():
for col_index, value in enumerate(row):
if pd.isna(value): # Check if the value is NaN (title rows will have NaN values)
label = ctk.CTkLabel(self.table, text=row["Item"], anchor='center', fg_color="#4d4d4d")
label.grid(row=row_index + 1, column=0, columnspan=len(self.dataframe.columns), padx=5, pady=5, sticky='ew')
break
elif col_index == 0: # Item Description
label = ctk.CTkLabel(self.table, text=str(value)[:60], anchor='w') # Display only first 30 characters
label.grid(row=row_index + 1, column=col_index, padx=5, pady=5, sticky='w')
self.table.columnconfigure(col_index, minsize=150) # Set a minimum width for the Item column
elif col_index == 1: # Rates
label = ctk.CTkLabel(self.table, text=f"{value:.2f}" if value != 0 else "0.00")
label.grid(row=row_index + 1, column=col_index, padx=5, pady=5)
elif col_index == 2: # Multiple
entry = ctk.CTkEntry(self.table)
entry.insert(0, f"{value:.2f}" if value != 0 else "0.00")
entry.grid(row=row_index + 1, column=col_index, padx=5, pady=5)
entry.bind("<FocusOut>", self.update_total(row_index, entry))
entry.bind("<Return>", self.update_total(row_index, entry)) # Update on pressing Enter key
self.entries[(row_index, col_index)] = entry
else: # Total
label = ctk.CTkLabel(self.table, text=f"{value:.2f}" if value != 0 else "0.00")
label.grid(row=row_index + 1, column=col_index, padx=5, pady=5)
def update_total(self, row_index, entry):
def callback(event):
try:
item_description = self.dataframe.at[row_index, 'Item']
print(f"Updating row: {row_index}, Item: {item_description}")
if pd.isna(self.dataframe.at[row_index, 'RATES']): # Check if the row is a title row
print(f"Skipping title row during update_total: {item_description}")
return
multiple = float(entry.get()) if entry.get() else 0.0 # Ensure float and handle empty input
rate = float(self.dataframe.iloc[row_index, 1]) # Assuming "RATES" is the second column
total = round(rate * multiple, 2) # Ensure 2 decimal places for "Total"
self.dataframe.at[row_index, 'MULTIPLE'] = multiple
self.dataframe.at[row_index, 'TOTAL'] = total
# Update the corresponding labels in the table
self.update_labels(row_index + 1, total)
except ValueError:
print(f"Error: Invalid input for 'Multiple' at row {row_index}")
return callback
def update_labels(self, row_index, total):
for col_index, value in enumerate(self.dataframe.iloc[row_index - 2]):
if col_index == 3: # "Total" column
total_label = self.table.grid_slaves(row=row_index, column=col_index)[0]
total_label.configure(text=f"{total:.2f}")
def update_all_totals(self):
for (row_index, col_index), entry in self.entries.items():
item_description = self.dataframe.at[row_index, 'Item']
print(f"Processing row: {row_index}, Item: {item_description}")
if pd.isna(self.dataframe.at[row_index, 'RATES']): # Check if the row is a title row
print(f"Skipping title row during update_all_totals: {item_description}")
continue
multiple = float(entry.get()) if entry.get() else 0.0 # Ensure float and handle empty input
rate = float(self.dataframe.iloc[row_index, 1]) # Assuming "RATES" is the second column
total = round(rate * multiple, 2) # Ensure 2 decimal places for "Total"
self.dataframe.at[row_index, 'MULTIPLE'] = multiple
self.dataframe.at[row_index, 'TOTAL'] = total
self.update_labels(row_index + 1, total)
def submit(self):
try:
self.update_all_totals()
# Remove title rows before saving
self.dataframe = self.dataframe.dropna(subset=['RATES'])
self.dataframe['MULTIPLE'] = self.dataframe['MULTIPLE'].astype(float).fillna(0)
self.dataframe['TOTAL'] = self.dataframe['TOTAL'].astype(float).fillna(0)
self.reviewed_by_value = self.reviewed_by.get() # Ensure this captures the input value
print("Updated DataFrame before closing the editor:n", self.dataframe)
print("Reviewed by (inside submit):", self.reviewed_by_value) # Print inside submit method
self.quit()
self.destroy()
except Exception as e:
print(f"Error: {e}")
# Display the DataFrame editor for user input
app = DataFrameEditor(template, corresponding_value, selected_month)
app.mainloop()
updated_template = app.dataframe
reviewed_by = app.reviewed_by_value
I have a similar script that works fine in another implementation, using the same environment. Strangely, for the code I’ve posted here, I’ve had to change the way the logo.png is loaded by setting “master=self” – otherwise, it wouldn’t recognize the file in the directory. I feel like that is related, but I’m not sure why the issue occured.