我有一个中等复杂的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的内置事件系统
我的代码通过调用处理程序对象的方法,避免了类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())