Python 在对象之间访问tkinter中的事件

Python 在对象之间访问tkinter中的事件,python,events,tkinter,callback,Python,Events,Tkinter,Callback,在这个示例python/tkinter脚本中,我有3个类。我的第一个类沙盒设置了一个带有两个选项卡的笔记本,每个选项卡都是一个单独的类对象 这个脚本不是我正在编写的真实脚本,它只是试图在这里添加一些简单的内容来帮助解释我的问题 我正在尝试确定如何将PageOne对象的更改通知PageTwo对象。例如,当数据被添加到PageOne的列表框中时,我希望PageTwo收到更改通知,并能够访问添加的数据。感谢您的指导 from tkinter import * from tkinter import t

在这个示例python/tkinter脚本中,我有3个类。我的第一个类沙盒设置了一个带有两个选项卡的笔记本,每个选项卡都是一个单独的类对象

这个脚本不是我正在编写的真实脚本,它只是试图在这里添加一些简单的内容来帮助解释我的问题

我正在尝试确定如何将PageOne对象的更改通知PageTwo对象。例如,当数据被添加到PageOne的列表框中时,我希望PageTwo收到更改通知,并能够访问添加的数据。感谢您的指导

from tkinter import *
from tkinter import ttk


class PageOne:
    def __init__(self):
        pass

    def init_ui(self, page):
        self.lb_test_one = Listbox(page, selectmode=SINGLE)
        self.lb_test_one.grid(row=0, column=0, columnspan=10, sticky=W + E)
        self.lb_test_one.configure(exportselection=False)
        self.lb_test_one.bind('<<ListboxSelect>>', self.on_test_one_selected)

        # UI Row 3
        self.b_add_data = Button(page, text="Add Data", command=self.on_add_data)
        self.b_add_data.grid(row=1, column=0, columnspan=10, sticky=N + S + E + W)
        return page

    counter = 0
    def on_add_data(self):
        self.lb_test_one.insert(self.counter, 'Test Data: {}'.format(self.counter))
        self.counter += 1

    def on_test_one_selected(self, evt):
        event_data = evt.widget
        if len(event_data.curselection()) > 0:
            index = int(event_data.curselection()[0])
            value = event_data.get(index)


class PageTwo:
    def __init__(self):
        pass

    def init_ui(self, page):
        self.lb_test_two = Listbox(page, selectmode=SINGLE)
        self.lb_test_two.grid(row=0, column=0, columnspan=10, sticky=W + E)
        self.lb_test_two.configure(exportselection=False)

        return page


class SandBox:
    root_tk = None
    def __init__(self):
        self.root_tk = Tk()
        self.root_tk.geometry("250x350")
        nb = ttk.Notebook(self.root_tk)
        nb.add(PageOne().init_ui(ttk.Frame(nb)), text='Tab One')
        nb.add(PageTwo().init_ui(ttk.Frame(nb)), text='Tab Two')
        nb.pack(expand=1, fill="both")
        self.root_tk.mainloop()

if __name__ == "__main__":
    SandBox()
从tkinter导入*
从tkinter导入ttk
第一页:
定义初始化(自):
通过
def初始用户界面(自身,第页):
self.lb_test_one=Listbox(第页,选择模式=SINGLE)
self.lb_test_one.grid(行=0,列=0,列跨度=10,粘性=W+E)
self.lb_test_one.configure(exportselection=False)
self.lb_test_one.bind(“”,self.on_test_one_selected)
#UI第3行
self.b_add_data=按钮(第页,text=“add data”,command=self.on_add_data)
self.b_add_data.grid(行=1,列=0,列跨度=10,粘性=N+S+E+W)
返回页
计数器=0
def on_添加_数据(自身):
self.lb_test_one.insert(self.counter,'testdata:{}.format(self.counter))
self.counter+=1
def on_test_one_selected(自我、evt):
事件\ u数据=evt.widget
如果len(event_data.curselection())>0:
index=int(事件\数据.curselection()[0])
value=event_data.get(索引)
第二类:
定义初始化(自):
通过
def初始用户界面(自身,第页):
self.lb_test_two=列表框(第页,选择模式=单一)
self.lb_test_two.grid(行=0,列=0,列跨度=10,粘性=W+E)
self.lb_test_two.configure(exportselection=False)
返回页
类沙箱:
root_tk=无
定义初始化(自):
self.root_tk=tk()
自根坐标几何(“250x350”)
nb=ttk.Notebook(self.root\u tk)
nb.add(PageOne().init_ui(ttk.Frame(nb)),text='Tab One')
nb.add(PageTwo().init_ui(ttk.Frame(nb)),text='Tab Two')
注意:包装(扩展=1,填充=“两个”)
self.root_tk.mainloop()
如果名称=“\uuuuu main\uuuuuuuu”:
沙盒()

使用多个类,您可以将帧或对象传递给应该与其他类交互的类

您甚至可以使用变量名从类外部处理类属性

下面是一个示例代码,展示了如何与从一个类到另一个类以及从外部类到主类实例的数据进行交互

import tkinter as tk


class LeftFrame(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.l_text = tk.Text(self.parent, width = 40, height = 10)
        self.l_text.grid(row=0, column=0)
        self.l_text.bind("<Key>", self.return_data)

    def return_data(self, event):
        data = self.l_text.get(1.0, tk.END)
        # uses the variable name "app" assigned to the main window class.
        # then calls a method inside that class to append the data from another class text box
        app.add_to_right_frame(data) 


class RightFrame(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.r_text = tk.Text(self.parent, width = 40, height = 10)
        self.r_text.grid(row=0, column=1)
        self.r_text.bind("<Key>", self.return_data)

    def return_data(self, event):
        data = self.r_text.get(1.0, tk.END)
        # uses the variable name "app" assigned to the main window class.
        # then calls a method inside that class to append the data from another class text box
        app.add_to_left_frame(data)


class MainWindow(tk.Frame):

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

        #makes a class attribute of each frame so it can be manipulated later            
        self.f1 = LeftFrame(self.main_frame)
        self.f2 = RightFrame(self.main_frame)

    def add_to_left_frame(self, data):
        self.f1.l_text.delete(1.0, tk.END)
        self.f1.l_text.insert(1.0, data)

    def add_to_right_frame(self, data):
        self.f2.r_text.delete(1.0, tk.END)
        self.f2.r_text.insert(1.0, data)


if __name__ == "__main__":
    root = tk.Tk()
    app = MainWindow(root)
    root.mainloop()
将tkinter作为tk导入
类LeftFrame(tk.Frame):
定义初始化(自身,父级):
tk.Frame.\uuuu init\uuuuu(自,父)
self.parent=parent
self.l_text=tk.text(self.parent,宽度=40,高度=10)
self.l_text.grid(行=0,列=0)
self.l\u text.bind(“,self.return\u数据)
def返回_数据(自身、事件):
data=self.l_text.get(1.0,tk.END)
#使用分配给主窗口类的变量名“app”。
#然后调用该类内的方法来附加来自另一个类文本框的数据
将应用添加到右框(数据)
类RightFrame(传统框架):
定义初始化(自身,父级):
tk.Frame.\uuuu init\uuuuu(自,父)
self.parent=parent
self.r_text=tk.text(self.parent,宽度=40,高度=10)
self.r_text.grid(行=0,列=1)
self.r\u text.bind(“,self.return\u数据)
def返回_数据(自身、事件):
data=self.r_text.get(1.0,tk.END)
#使用分配给主窗口类的变量名“app”。
#然后调用该类内的方法来附加来自另一个类文本框的数据
应用程序。将\添加到\左\帧(数据)
类主窗口(tk.Frame):
定义初始化(自,根):
tk.Frame.\uuuu init\uuuu(self,root)
self.master=root
self.main_frame=tk.frame(self.master)
self.main\u frame.grid(行=0,列=0,sticky=“nsew”)
#为每个帧生成一个类属性,以便以后可以对其进行操作
self.f1=左框架(self.main_框架)
self.f2=右帧(self.main_帧)
def添加到左帧(自身,数据):
self.f1.l_text.delete(1.0,tk.END)
self.f1.l_text.insert(1.0,数据)
def添加到右帧(自身,数据):
self.f2.r_text.delete(1.0,tk.END)
self.f2.r_text.insert(1.0,数据)
如果名称=“\uuuuu main\uuuuuuuu”:
root=tk.tk()
app=main窗口(根)
root.mainloop()

结果是一个窗口,其中有两个由外部类创建的文本框,并且能够在一个框中键入并在另一个框中显示,反之亦然。

一个解决方案是将
沙箱设置为一个控制器,您可以用它实现发布/订阅系统。每个页面都可以调用控制器的
notify
方法,然后控制器可以调用所有订阅者的
notify
方法

它首先为每个页面提供控制器的参考:

class Sandbox:
    def __init__(self):
        ...
        page1 = PageOne(controller=self)
        page2 = PageTwo(controller=self)
然后,每个页面都可以调用控制器上的一个方法来发送信息:

class PageOne:
    def __init__(self, controller):
        self.controller = controller
        ...
    def on_add_data(self):
        ...
        self.controller.notify("some data")
SandBox
可以迭代所有已知页面(通过保存对列表中每个页面的引用),也可以提供一种方法,以便其他页面可以请求添加到列表中(即:a
subscribe
方法)

作为前者的一个例子,它很简单

class SandBox:
    def __init__(self):
        ...
        page1 = PageOne(controller=self)
        page2 = PageTwo(controller=self)
        self.subscribers=[page1, page2]
        ...
    def notify(self, data):
        for page in self.subscribers:
            page.notify(data)
然后,您需要为每个页面实现
notify
,以便在它接收数据时执行您希望它执行的任何操作

这只是基本概念。发布/订阅系统必须允许您订阅特定类型的事件,以便所有数据不会一直发送到每个订阅者。例如PageOne可能不关心PageOne何时更改

您可以通过在控制器中引入一个
subscribe
方法来实现这一点,您可以在控制器中传入i的类型
def __init__(...):
    ...
    # tell the controller that when a notification with a type of
    # "list_changed" is received it should call self.on_list_changed
    self.controller.subscribe("list_changed", self.on_list_changed)
    ...

def on_list_changed(self, data):
    print("data received:", data)
# send a "list_changed" message with some data
self.controller.notify("list_changed", "some data...")
self.subscribers = {}
...
def subscribe(self, message_type, callback):
    self.subscriptions.setdefault(message_type, [])
    self.subscriptions[message_type].append(callback)

def notify(self, message_type, data):
    for callback in self.subscriptions[message_type]:
        # call the callback, sending it the data
        callback(data)