Передача данных через классы 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]()
Сообщите мне, если все работает нормально или вам нужна дополнительная информация.
Спасибо