从 tkinter 中的另一个 class 更新数据框

Update dataframe from another class in tkinter

我想更新 tkinter 中显示的数据框,从另一个 class 获取数据。

在我的应用程序中,我使用 classes 定义了框架。通过更改 class 中的输入参数,数据框应在另一个 class.

中更新

例如,我select class CommandsOptionMenu中的乘数和class中显示的数据框中的B列] Table 应按此乘数更新。但是当我更改乘数时,数据框不会更新。我在这里使用 Treeview 来显示数据框。

当我启动应用程序时,GUI 如下所示。右侧的窄条纹是空数据框。我将它初始化为空,即使我更改了乘数,它仍然是空的。相反,它应该看起来像之前的屏幕截图。

当然,完整的应用程序要复杂得多,但在这里我为了问题的缘故简化了一些事情。实际上,数据框包含很多列,计算相当复杂。

我尝试通过控制器方法传递对象。例如,在 class Table(显示数据框)中,我定义:

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

我(错误地)假设要在此帧中显示的数据帧 self.metrics 通过控制器对象调用函数 get_page() 得到更新。函数 get_page 从 class Commands 中获取对象 metrics 并在主 class sampleApp 中定义为:

def get_page(self, page_class):
    return self.frames[page_class]

其他 class 命令包含相同的方法父/控制器以允许对象在 class 之间传递。

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()

在 class Commands 中,我将数据框 self.metrics 初始化为空(但这并不重要)。稍后在 class Commands 中,我使用函数 calculate_df 更新数据框,该函数不 return 任何东西,应该保持不变。因此,那里计算的数据框对象定义为global.

我使用调用以下两行的 OptionMenu 方法更新 class Commands 中的数据框 self.metrics

calculate_df(mult = self.mult)
self.metrics = df

这里的dfglobal,我故意不直接用命令定义self.metrics = calculate_df(...)

函数 calculate_df 创建一个包含两列的数据框,其中 B 列相乘。例如乘数 2 df 变为:

   A  B
0  0  0
1  1  2
2  2  4
3  3  6
4  4  8

我post下面是完整的代码。

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()

您可以创建一个 static method 来实现此目的。

您将需要阅读一些有关该主题的内容。

但总之。您在 Table class 中定义了一个静态方法,然后您可以在命令 class 中调用该函数而无需初始化 Table class 的实例。可以使用 Table.my_method(value).

调用静态方法

首先将tv1更改为实例变量self.tv1,将嵌套函数display_metrics()更改为Table的class函数。还将所需的 metrics 作为参数传递给 display_metrics()

然后你可以在Commands里面的嵌套函数set_multiplier()里面直接调用Table.display_metrics() 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')

        # changed tv1 to instance variable self.tv1
        self.tv1 = ttk.Treeview(table_frame, show="headings")
        self.tv1.pack()

    # change display_metrics() to class function with added argument metrics
    def display_metrics(self, metrics):
        self.tv1.delete(*self.tv1.get_children())
        self.tv1["column"] = list(metrics.columns)
        for column in self.tv1["columns"]:
            self.tv1.heading(column, text=column)  # set column heading

        df_rows = metrics.to_numpy().tolist()  # convert dataframe to list
        for row in df_rows:
            # inserts each list into the treeview.
            self.tv1.insert("", "end", values=row)


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
            
            # update table
            self.controller.get_page(Table).display_metrics(self.metrics)

        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)
...

请注意,最好将嵌套函数 set_multiplier() 也更改为 class 方法。