I would like to update a data frame displayed in tkinter getting the data from another class.
In my app, I have defined the frames using classes. By changing an input parameter in a class, the data frame should update in another class.
For instance, I select the multiplier in the OptionMenu within the class Commands and the column B in the data frame displayed in the class Table should be updated by this multiplier. But as I change the multiplier, the data frame does not update. I am using here Treeview to display the data frame.
As I start the app, the GUI appears like the following. The narrow stripe on the right is the empty data frame. I initialize it as empty and it remains empty even if I change the multiplier. While instead it should look like the previous screenshot.
Of course the full app is much more sophisticated, but here I simplify things for the sake of the question. Actually, the data frame contains many columns and the calculation is quite complex.
I try to pass the object through a controller method. For instance, in the class Table (which displays the data frame), I define:
class Table(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.parent = parent
self.controller = controller # save the reference to the controller in each class
...
get_metrics = self.controller.get_page(Commands)
self.metrics = get_metrics.metrics
Where I (wrongly) suppose that the data frame self.metrics to be displayed in this frame gets updated by calling the function get_page() via the controller object. The function get_page gets the object metrics from the class Commands and is defined in the main class sampleApp as simple as:
def get_page(self, page_class):
return self.frames[page_class]
The other class Commands contains the same method parent / controller to allow the objects to be passed among classes.
class Commands(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.parent = parent
self.controller = controller # save the reference to the controller in each class
self.metrics = pd.DataFrame()
In the class Commands, I initialize the data frame self.metrics as empty initially (but this is not important). Later in the class Commands, I update the data frame with the function calculate_df, which is a function that does not return anything and should remain so. Therefore, the data frame object calculated there is defined as global.
I update the data frame self.metrics in the class Commands using an OptionMenu method that calls the following two lines:
calculate_df(mult = self.mult)
self.metrics = df
Here df is global and I intentionally not define with the direct command self.metrics = calculate_df(...).
The function calculate_df creates a data frame of two columns where the column B gets multiplied. For example with a multiplier of 2 df becomes:
A B
0 0 0
1 1 2
2 2 4
3 3 6
4 4 8
I post the full code in the following.
import numpy as np
import pandas as pd
pd.set_option('display.max_columns', 20)
pd.set_option('display.width', 200)
import tkinter as tk
from tkinter import ttk
def calculate_df(mult = 1.0):
global df
columns = [np.arange(5), np.arange(5) * mult]
df = pd.DataFrame(data=np.array(columns).T, columns=['A', 'B'])
class sampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "sampleApp")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frame = Commands(parent=container, controller=self)
self.frames[Commands] = frame
frame.grid(row=0, column=0)
# frame.pack()
self.show_frame(Commands)
frame = Table(parent=container, controller=self)
self.frames[Table] = frame
frame.grid(row=0, column=1)
# frame.pack()
self.show_frame(Table)
def show_frame(self, frame_name):
frame = self.frames[frame_name]
frame.tkraise()
def get_page(self, page_class):
return self.frames[page_class]
class Table(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.parent = parent
self.controller = controller # save the reference to the controller in each class
table_frame = tk.Frame(self, bd=1, relief=tk.RIDGE)
table_frame.pack(fill='x')
get_metrics = self.controller.get_page(Commands)
self.metrics = get_metrics.metrics
# columns = [np.arange(5), np.arange(5) * 2]
# df = pd.DataFrame(data=np.array(columns).T, columns=['A', 'B'])
# self.metrics = df
tv1 = ttk.Treeview(table_frame)
tv1.pack()
def display_metrics():
tv1.delete(*tv1.get_children())
tv1["column"] = list(self.metrics.columns)
tv1["show"] = "headings"
for column in tv1["columns"]:
tv1.heading(column, text=column) # set column heading
df_rows = self.metrics.to_numpy().tolist() # convert dataframe to list
for row in df_rows:
# inserts each list into the treeview.
tv1.insert("", "end", values=row)
display_metrics()
class Commands(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.parent = parent
self.controller = controller # save the reference to the controller in each class
self.metrics = pd.DataFrame()
side_frame = tk.Frame(self, relief=tk.RIDGE)
side_frame.pack()
buttons_frame = tk.Frame(side_frame, bd=1, relief=tk.RIDGE)
buttons_frame.pack(fill='x')
Lab1 = tk.Label(buttons_frame, text="multiplier", anchor=tk.W)
Lab1.grid(row=1, column=0)
def set_multiplier(*args):
self.mult = float(mult_var.get())
print("multiplier", self.mult)
calculate_df(mult = self.mult)
self.metrics = df
mult_var = tk.StringVar(self)
mult_var.set('')
mult_var.trace("w", set_multiplier)
opt_mult = tk.OptionMenu(buttons_frame, mult_var, *[1, 2, 3, 4])
opt_mult.grid(row=2, column=2, columnspan=1)
if __name__ == '__main__':
app = sampleApp()
app.geometry("+35+35")
app.mainloop()

