在tkinter

时间:2016-08-04 16:33:26

标签: python python-3.x user-interface 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类。由于项目仍然是原型,我一直在改变事物,我希望能够重命名{{1}其他东西,或者完全摆脱属于B类的小部件,或者使instance_a成为某个callback对象的子节点(在这种情况下PanedWindow需要被master替换)。

另一种方法是在应用程序类中放置一个方法,只要触发某个事件就会调用该方法:

winfo_toplevel()

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

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

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

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

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

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

1 个答案:

答案 0 :(得分:0)

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

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