通过 Tkinter 类传递数据帧

Posted

技术标签:

【中文标题】通过 Tkinter 类传递数据帧【英文标题】:Pass data frame through Tkinter classes 【发布时间】:2018-01-20 12:07:27 【问题描述】:

我正在使用 Python 2.7 和 Tkinter。我几乎是面向对象程序的新手。我有一个带有许多 Tkinter 窗口的长程序,有时我要求用户加载我用 Pandas 读取的 Excel 文件,并希望永久使用和更新该值(data 变量) .我现在这样做的方式是使用全局变量,但我知道它是危险的、低效的而且一点也不优雅。

尽管根据我的 gui 类的构建方式我可以执行 controller.show_frame(framename),但我最终还是自己构建了一些框架,以便数据变量能够自行更新。

我在 Stack Overflow 中阅读并尝试了一些答案,但可能实施错误:

尝试在 gui 类中创建一个字典,例如 self.app_data = data=[],filename="" 并从其他窗口更新它,这里的事情是我认为类 gui 只实例化一次,它创建了所有其他窗口类所以这不起作用。也许我在那里做错了什么。 (代码中未显示)。 尝试按照here 的建议做某事,但我无法让它发挥作用。

主框架是我需要用于其他目的的某种中间步骤;以下代码是我的程序的简化。

我知道这是一个可怕的噩梦代码!谢谢你:)

import Tkinter as tk
import pandas as pd 
import tkFileDialog
import tkMessageBox
global data, strat_columns, filename
data = pd.DataFrame([])
strat_columns = []
filename = ""

class gui(tk.Tk):

    data = pd.DataFrame([])
    filename = ""
    def __init__(self):
        tk.Tk.__init__(self)
        container = tk.Frame(self)
        container.pack(side="top",fill="both",expand=True)
        self.frames = 

        for F in (main_frame, first_frame):
            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")
        self.show_frame(main_frame)

    def show_frame(self,sel_frame):
        frame = self.frames[sel_frame]
        frame.tkraise()

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


class main_frame(tk.Frame):

    def __init__(self,parent,controller):

        tk.Frame.__init__(self,parent)
        self.parent = parent 
        self.controller = controller
        button_new = tk.Button(self,
                           text="New window",
                           command=lambda: self.button_new_callback())
        button_new.pack()

    def button_new_callback(self,*args,**kwargs):
        self.controller.show_frame(first_frame)


class first_frame(tk.Frame):

    def __init__(self,parent,controller):
        tk.Frame.__init__(self,parent)
        self.controller = controller
        self.parent = parent
        self.show_frame = controller.show_frame
        statusText.set("Press Browse button and browse for file, then press the Go button")
        label = tk.Label(self, text="Please load a file: ")
        label.pack()
        entry = tk.Entry(self, width=50)
        entry.pack()
        button_go = tk.Button(self,
                           text="Go",
                           command=lambda: self.button_go_callback(entry,statusText,message))
        button_browse = tk.Button(self,
                               text="Browse",
                               command=lambda: self.button_browse_callback(entry))
        button_go.pack()
        button_browse.pack()
        message = tk.Label(self, textvariable=statusText)
        message.pack()

    def button_browse_callback(self,entry):
        global filename
        filename = tkFileDialog.askopenfilename()
        entry.delete(0, tk.END)
        entry.insert(0, filename)

    def button_go_callback(self,entry,statusText,message):
        global data
        input_file = entry.get()
        data = pd.read_excel(filename)
        sf = second_frame(self.parent, self)
        sf.grid(row=0, column=0, sticky="nsew")
        sf.tkraise()

class second_frame(tk.Frame):
     pass

if __name__ == "__main__":

    my_gui = gui()
    my_gui.mainloop()
    my_gui.title("TEST")

【问题讨论】:

您可以在gui.__init__ 中启动datastrat_columnsfilename,并使用main_framefirst_frame 中的controller 对象访问它们,例如:self.controller.filename = '/some/file'。或者,您可以构建一个小类,例如:my_data 具有 3 个类变量:datastrat_columnsfilename。然后您可以直接访问它们(无需创建新实例)例如:my_data.filename = '/some/file' 正如 t.m.adam 提到的,您应该将两个变量 datafilename 移动到 __init__ 部分内。不要在类中使用全局。而是使用 self. 前缀将您的变量转换为类属性。这将消除对全局的需要。可以从类或其方法内部的任何位置访问类属性。 谢谢!我按照@t.m.adam 的建议创建了my_data 类,只要我将它们创建为sf = second_frame(self.parent, self); sf.grid(row=0, column=0, sticky="nsew"); sf.tkraise(),它就可以在窗口之间成功传递信息。如果我尝试以self.controller.show_frame(second_frame) 执行此操作,则会使用变量的初始值呈现窗口,并且它们相应的类将不会再次“实例化”(不确定这是否是正确的词)。关于为什么会发生这种情况的任何线索?谢谢! 为什么不尝试使用实例变量呢?我只提到了类变量方法作为全局变量的替代方法。 我只是想指出self.app_data = data=[],filename="" 不起作用,因为它不是一个有效的字典格式。它需要像self.app_data = data : [], filename : "" 字典使用冒号,左侧是键,右侧是数据。 【参考方案1】:

有几件事会导致您的程序无法正常运行。

我首先注意到的是全局变量的使用。这可以通过使用类属性来避免。

对于 class gui(tk.Tk): 行下方的 2 个变量,您需要将它们移动到 __init__ 部分,以便可以实例化这些变量。您还需要将它们变成类属性,以便其他方法甚至其他类可以与它们交互。我们可以通过在变量名称中添加self. 前缀来做到这一点。

类似于以下内容:

self.data = pd.DataFrame([])
self.filename = ""

要访问gui 类的方法/属性,您需要将gui 类的对象传递给使用它的其他类。

您的代码中的statusText 未定义,因此set() 不会在这里工作。只需将self.statusText 添加为类属性即可。

您的某些小部件不需要分配给变量名称,因为没有对它们进行任何编辑。例如:

label = tk.Label(self, text="Please load a file: ")
label.pack()

这可以简单地改为:

tk.Label(self, text="Please load a file: ").pack()

这将有助于减少您编写的代码量并保持名称空间更整洁。

有几种方法可以纠正这一切,但最简单的方法是将这段代码移到一个类中。您提供的代码没有充分的理由将多个框架与主要的gui 类分开。

以下代码是您的代码的重写版本,它使用一个类来完成您的代码试图完成的任务,并将所需的代码量减少了大约 30 多行。我相信它可以进一步完善,但这个例子应该会有所帮助。

import Tkinter as tk
import pandas as pd
import tkFileDialog

class gui(tk.Frame):


    def __init__(self, master, *args, **kwargs):
        tk.Frame.__init__(self, master, *args, **kwargs)

        self.master = master
        self.data = pd.DataFrame([])
        self.filename = ""
        self.strat_columns = []

        self.main_frame()
        self.first_frame()
        self.mainframe.tkraise()

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

    def main_frame(self):
        self.mainframe = tk.Frame(self.master)
        self.mainframe.grid(row=0, column=0, sticky="nsew")

        tk.Button(self.mainframe, text="New window", 
                  command=lambda: self.firstframe.tkraise()).pack()


    def first_frame(self):
        self.firstframe = tk.Frame(self.master)
        self.firstframe.grid(row=0, column=0, sticky="nsew")

        self.statusText = tk.StringVar()
        self.statusText.set("Press Browse button and browse for file, then press the Go button")
        label = tk.Label(self.firstframe, text="Please load a file: ").pack()
        self.first_frame_entry = tk.Entry(self.firstframe, width=50)
        self.first_frame_entry.pack()
        tk.Button(self.firstframe, text="Go", command=self.button_go_callback).pack()
        tk.Button(self.firstframe, text="Browse", command=self.button_browse_callback).pack()
        self.message = tk.Label(self.firstframe, textvariable=self.statusText)
        self.message.pack()


    def button_browse_callback(self):
        self.filename = tkFileDialog.askopenfilename()
        self.first_frame_entry.delete(0, tk.END)
        self.first_frame_entry.insert(0, self.filename)

    def button_go_callback(self):
        self.data = pd.read_excel(self.filename)


if __name__ == "__main__":
    root = tk.Tk()
    root.title("TEST")
    my_gui = gui(root)
    root.mainloop()

【讨论】:

谢谢!到目前为止,它正在工作。当我扩展我的程序时,在callback 方法之一中,我想在对数据进行转换后打开一个窗口second_framedef second_frame(self): self.secondframe = tk.Frame(self.master) self.secondframe.grid(row=0, column=0, sticky="nsew") ... 我正在从 callback 方法之一内部执行 self.secondframe.tkraise()。现在的问题是一个窗口正在打开,但没有其他任何事情发生(即使我只是想添加一些按钮和文本)。打开新窗口的正确方法是什么? @sarangof 创建后,您应该可以正常添加任何小部件到self.secondframe @sarangof 听起来你可能有一个新问题,应该是一个新问题。 谢谢@mike-smt,你是对的,问题不同了,它已经解决了。 @sarangof 所以你的新框架问题现在解决了吗?【参考方案2】:

在我看来,您将数据和 GUI 捆绑得太多了。如果将来您想显示其他内容怎么办?我会使用更通用的方法:我会创建一个 DataProvider 类,它会为您读取并返回数据:

数据提供者

import pandas as pd

    class DataProvider(object):

        def __init__(self):
            self._excel = 
            self._binary = 

        def readExcel(self, filename):
            self._excel[filename] = pd.read_excel(filename)

        def excel(self):
            return self._excel

        def readBinary(self, filename):
            self._binary[filename] = open(filename,"rb").read()

        def binary(self):
            return self._binary

使用这个类,您可以获得可以在 GUI 中显示的数据:

gui.py

from Tkinter import *
from DataProvider import *
import binascii

#example data
dp = DataProvider()
dp.readExcel("file.xlsx")
dp.readBinary("img.png")


root = Tk()
frame = Frame(root)
frame.pack()

bottomframe = Frame(root)
bottomframe.pack( side = BOTTOM )

w = Label(bottomframe, text=dp.excel()["file.xlsx"])
w.pack()


w = Label(bottomframe, text=binascii.hexlify(dp.binary()["img.png"][:5]))
w.pack()

root.mainloop()

如果一切正常,您应该有一个小 GUI,显示 Excel 文件的内容和图像的前几个字节。

如果一切正常,或者您需要更多信息,请告诉我。

谢谢

【讨论】:

以上是关于通过 Tkinter 类传递数据帧的主要内容,如果未能解决你的问题,请参考以下文章

通过作为参数传递的一组列聚合数据帧

单击按钮后如何将输入值(TKinter)传递给其他类中的函数并使用传递的值运行函数[重复]

如何通过tkinter绑定传递更多参数

如何将变量值从一个文件中的一个类传递给另一个文件中的另一个类python tkinter

python tkinter, 通过lambda表达式传递参数到按钮的点击事件函数

将 pandas 数据帧传递给 FastAPI 用于 NLP ML