Передача данных через классы Tkinter

Я использую Python 2.7 и Tkinter. Я почти новичок в объектно-ориентированных программах. У меня есть длинная программа со многими окнами Tkinter, и в какой-то момент я прошу пользователя загрузить файл Excel, который я прочитал с помощью Pandas, и хочу постоянно использовать и обновлять это значение (переменной данных). То, как я это делаю сейчас, - это глобальные переменные, но я знаю, что это опасно, неэффективно и не очень элегантно.

Несмотря на то, что я мог делать controller.show_frame (framename) с учетом того, как мой класс gui построен, я в конечном итоге создал некоторые из фреймов, так что переменная данных обновила бы себя.

Я прочитал и попробовал несколько ответов в Stack Overflow, но, возможно, неправильно использовал их:

  • Попробовал создать словарь внутри класса gui, что-то вроде self.app_data = {data=[],filename=""} и обновить его из других окон. Дело в том, что я считаю, что класс gui запускается только один раз и он создает все остальные классы окон так что это не сработало. Возможно, я сделал что-то не так. (не отображается в коде).
  • Пытался сделать что-то, что было предложено здесь, но я просто не мог заставить его работать.

Основной кадр - это своего рода промежуточный шаг, который мне нужен для других целей; следующий код - это упрощение моей программы.

Я знаю, что это ужасный кошмарный код! Спасибо:)

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

Ответы

Ответ 1

Есть несколько вещей, которые вызывают проблемы с правильной работой вашей программы.

Первое, что я заметил, это использование глобальных переменных. Этого можно избежать с помощью атрибутов класса.

Для двух переменных, которые у вас чуть ниже строки class gui(tk.Tk):, вам нужно переместить их в раздел __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()

Ответ 2

на мой взгляд, вы слишком много связываете данные и графический интерфейс. Что делать, если в будущем вы хотите отобразить что-то еще? Я бы использовал более общий подход: я бы создал класс 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.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()

Если все работает хорошо, у вас должен быть небольшой графический интерфейс, показывающий содержимое файла Excel и первые несколько байтов изображения.

Пример Gui

Сообщите мне, если все работает нормально или вам нужна дополнительная информация.

Спасибо