tkinter中父窗口小部件和子窗口小部件之间的信令



我有一个中等复杂的GUI,我正在构建它,用于与一些模拟进行交互和观察。我希望能够随着项目的进展继续重构和添加功能。出于这个原因,我希望应用程序中不同小部件之间的耦合尽可能松散。

我的应用程序的结构如下:

import tkinter as tk
class Application(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.instance_a = ClassA(self)
        self.instance_b = ClassB(self)
        # ... #
class ClassA(tk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # ... #
class ClassB(tk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # ... #
def main():
    application = Application()
    application.mainloop()
if __name__ == '__main__':
    main()

我希望能够在一个小部件中执行一些操作(例如在Treeview小部件中选择一个项目或单击画布的一部分),从而更改另一个小程序的状态。

一种方法是在A类中有以下代码:

self.bind('<<SomeEvent>>', self.master.instance_b.callback())

附带B类代码:

def callback(self): print('The more that things change, ')

这种方法的问题是,类A必须了解类B。由于项目仍然是一个原型,我一直在改变事情,我希望能够将callback重命名为其他东西,或者完全去掉属于类B的小部件,或者使instance_A成为某个PanedWindow对象的子对象(在这种情况下,master需要被winfo_toplevel()取代)。

另一种方法是在应用程序类中放入一个方法,每当触发某个事件时就会调用该方法:

class Application(tk.Tk):
    # ... #
    def application_callback():
        self.instance_b.callback()

并修改A类中的绑定事件:

self.bind('<<SomeEvent>>', self.master.application_callback())

这肯定更容易维护,但需要更多的代码。它还要求应用程序类了解类B中实现的方法,以及instance_B在小部件层次结构中的位置。在一个完美的世界里,我希望能够做这样的事情:

# in class A:
self.bind('<<SomeEvent>>', lambda _: self.event_generate('<<AnotherEvent>>'))
# in class B:
self.bind('<<AnotherEvent>>', callback)

这样,如果我在一个小部件中执行一个操作,第二个小部件将自动知道以某种方式响应,而任何一个小组件都不知道另一个的实现细节。经过一些测试和挠头,我得出结论,使用tkinter的事件系统,这种行为是不可能的。因此,以下是我的问题:

  1. 这种想要的行为真的不可能吗
  2. 这是个好主意吗
  3. 有没有更好的方法来实现我想要的模块化程度
  4. 我可以使用哪些模块/工具来代替tkinter的内置事件系统

我的代码通过调用处理程序对象的方法,避免了类A必须了解类B内部的问题。在下面的代码中,类Scanner中的方法不需要了解ScanWindow实例的内部。Scanner类的实例包含对处理程序类实例的引用,并通过Handler类的方法与ScannerWindow的实例通信。

# this class could be replaced with a class inheriting 
# a Tkinter widget, threading is not necessary
class Scanner(object):
    def __init__(self, handler, *args, **kw):
        self.thread = threading.Thread(target=self.run)
        self.handler = handler
    def run(self):
        while True:
            if self.handler.need_stop():
                break
            img = self.cam.read()
            self.handler.send_frame(img)
class ScanWindow(tk.Toplevel):
    def __init__(self, parent, *args, **kw):
        tk.Toplevel.__init__(self, master=parent, *args, **kw)
        # a reference to parent widget if virtual events are to be sent
        self.parent = parent
        self.lock = threading.Lock()
        self.stop_event = threading.Event()
        self.frames = []
    def start(self):
        class Handler(object):
            # note self and self_ are different
            # self refers to the instance of ScanWindow
            def need_stop(self_):
                return self.stop_event.is_set()
            def send_frame(self_, frame):
                self.lock.acquire(True)
                self.frames.append(frame)
                self.lock.release()
                # send an event to another widget 
                # self.parent.event_generate('<<ScannerFrame>>', when='tail')
            def send_symbol(self_, data):
                self.lock.acquire(True)
                self.symbols.append(data)
                self.lock.release()
                # send an event to another widget
                # self.parent.event_generate('<<ScannerSymbol>>', when='tail')
        self.stop_event.clear()
        self.scanner = Scanner(Handler())

最新更新