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

该脚本不是我正在使用的真实脚本,它只是尝试在此处添加一些简单内容以帮助解释我的问题。

我试图确定如何从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()

最佳答案

一种解决方案是将SandBox设置为控制器,使用该控制器可以实现发布/订阅系统。每个页面都可以调用控制器的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可以遍历所有已知页面(通过将对每个页面的引用保存在列表中),也可以提供一种方法,以便可以将其他页面要求添加到列表中(即: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方法并在其中传递您要订阅的信息的类型以及调用该类型消息的函数来实现此目的。在这种情况下,“类型”非常类似于Tkinter中的事件。它可以实现为简单的字符串,不必花哨。

在PageTwo中:

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)


在PageOne中:

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

09-27 13:38