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

Posted

技术标签:

【中文标题】tkinter 中父子小部件之间的信令【英文标题】:Signaling between parent and child widgets in tkinter 【发布时间】:2016-12-10 21:41:21 【问题描述】:

我正在构建一个中等复杂的 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 的事件系统,这种行为是不可能的。所以,这是我的问题:

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

【问题讨论】:

您好,您找到合适的解决方案了吗? 【参考方案1】:

我在answer 中的代码通过调用处理程序对象的方法避免了A 类必须了解B 类内部的问题。在Scanner 类中的以下代码方法中,不需要了解ScanWindow 实例的内部结构。 Scanner类的实例包含对handler类实例的引用,并通过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())

【讨论】:

我想要一些澄清,以帮助更好地理解您的答案的基本特征。是否绝对需要使用线程模块(对于非 OpenCV 应用程序)?您生成的扫描仪事件绑定了哪些功能?这些行中哪个对象是父对象? 感谢您提供这个有趣的模式,但我不明白这如何实现我在问题中描述的那种模块化。 Scanner 类调用Handler 的方法,该方法是在ScanWindow 范围内定义的类。如果我错了,请纠正我,但我认为这或多或少相当于直接调用ScanWindow 的方法。 Scanner 可能不知道ScanWindow,但只能通过花招。目标是从层次结构中的任何其他小部件触发一个小部件中的事件,只有事件名称是共同的。

以上是关于tkinter 中父子小部件之间的信令的主要内容,如果未能解决你的问题,请参考以下文章

tkinter 中的框架对象没有对小部件进行分组

如何在 tkinter 中找出当前小部件的大小?

Python清除在Tkinter小部件中使用的列表

如何在 tkinter Text 小部件中突出显示文本

获取 Tkinter Entry 小部件的内容

在 Tkinter 文本小部件中显示命令行结果