【问题标题】:Tkinter generate and invoke virtual event between different widgetsTkinter 在不同的小部件之间生成和调用虚拟事件
【发布时间】:2015-10-26 05:38:15
【问题描述】:

在 tkinter 中编写一些简单的 gui 应用程序时,我遇到了一些小问题。假设我有自定义菜单小部件(派生自 tk.Menu)和自定义画布小部件(派生自 tk.Canvas)。

我想从菜单回调函数生成事件并在画布小部件中调用它。我需要这样做,因为将来我想添加更多小部件,这些小部件应该对菜单中的点击位置做出反应。

我试图这样做:

自定义菜单:

class MainMenu(tk.Menu):

    def __init__(self, parent):
       tk.Menu.__init__(self, parent)
       self.add_comand(label='foo', self._handler)
       return

    def _handler(self, *args):
        print('handling menu')       
        self.event_generate('<<abc>>')
        return

自定义画布:

class CustomCanvas(tk.Canvas): 
    def __init__(self, parent, name=''):
        tk.Canvas.__init__(self, parent)
        self.bind('<<abc>>', self.on_event)
        return

    def on_event(self, event):
       print(event)
       return

当我点击菜单中的位置时,_handler回调被正确调用并生成事件,但on_event回调没有被调用。我尝试添加 when='tail' 参数,添加 self.update() 等,但没有任何结果。有人知道怎么做吗?

【问题讨论】:

  • 我不认为创建这两个项目需要两个类。尝试一次完成,也许会奏效。

标签: python events tkinter virtual


【解决方案1】:

这是我创建自定义虚拟事件的示例代码。我创建了这段代码来模拟需要很长时间才能响应数据的调用服务器:

#Custom Virtual Event

try:
    from Tkinter import *
    import tkMessageBox
except ImportError:
    try:
        from tkinter import *
        from tkinter import messagebox
    except Exception:
        pass

import time
from threading import Thread

VirtualEvents=["<<APP_DATA>>","<<POO_Event>>"]

def TS_decorator(func):
    def stub(*args, **kwargs):
        func(*args, **kwargs)

    def hook(*args,**kwargs):
        Thread(target=stub, args=args).start()

    return hook

class myApp:
    def __init__(self):
        self.root = Tk()
        self.makeWidgets(self.root)
        self.makeVirtualEvents()
        self.state=False
        self.root.mainloop()

    def makeWidgets(self,parent):
        self.lbl=Label(parent)
        self.lbl.pack()
        Button(parent,text="Get Data",command=self.getData).pack()

    def onVirtualEvent(self,event):
        print("Virtual Event Data: {}".format(event.VirtualEventData))
        self.lbl.config(text=event.VirtualEventData)

    def makeVirtualEvents(self):
        for e in VirtualEvents:
            self.root.event_add(e,'None') #Can add a trigger sequence here in place of 'None' if desired
            self.root.bind(e, self.onVirtualEvent,"%d")

    def FireVirtualEvent(self,vEvent,data):
        Event.VirtualEventData=data
        self.root.event_generate(vEvent)


    def getData(self):
        if not self.state:
            VirtualServer(self)
        else:
            pooPooServer(self)

        self.state = not self.state


@TS_decorator
def VirtualServer(m):
    time.sleep(3)
    m.FireVirtualEvent(VirtualEvents[0],"Hello From Virtual Server")

@TS_decorator
def pooPooServer(m):
    time.sleep(3)
    m.FireVirtualEvent(VirtualEvents[1],"Hello From Poo Poo Server")


if __name__=="__main__":
    app=myApp()

在此代码示例中,我正在创建自定义虚拟事件,一旦模拟服务器完成检索数据就会调用这些事件。事件处理程序 onVirtualEvent 绑定到根级别的自定义虚拟事件。

单击按钮时,模拟服务器将在单独的执行线程中运行。我正在使用自定义装饰器 TS_decorator 来创建调用模拟服务器的执行线程。

我的方法真正有趣的部分是,我可以通过调用 FireVirtualEvent 方法将从模拟服务器检索到的数据提供给事件处理程序。在此方法中,我向 Event 类添加了一个自定义属性,该属性将保存要传输的数据。然后,我的事件处理程序将使用此自定义属性从服务器中提取数据。

虽然概念简单,但此示例代码也缓解了在处理需要很长时间执行的代码时 GUI 元素不更新的问题。由于所有工作代码都在单独的执行线程中执行,因此对函数的调用会很快返回,这允许 GUI 元素更新。请注意,我还将对 myApp 类的引用传递给模拟服务器,以便它们可以在数据可用时调用其 FireVirtualEvent 方法。

【讨论】:

    【解决方案2】:

    您需要将绑定添加到获取事件的小部件。在您的情况下,您正在菜单上生成事件,因此您需要绑定到菜单。

    您还可以在画布上生成事件,并将绑定保留在画布上。或者,将事件与根窗口关联,并绑定到根窗口。

    在某些情况下 tkinter 本身采用的一种常用技术是在根窗口上生成事件,然后在根窗口(或所有带有bind_all 的窗口)上为该事件创建一个绑定.然后,单个绑定必须通过某种方式(通常,例如,通过获得键盘焦点的窗口)确定要影响哪个窗口。

    当然,如果您有办法确定哪个小部件获得绑定,您可以在生成事件时使用该方法直接在相应的小部件上生成事件。

    有关详细信息,请参阅Events and Bindings,特别是该文档中标题为“实例和类绑定”的部分。

    【讨论】:

    • 感谢布莱恩的回答!无论如何,我想使用事件作为设置不同小部件之间通信的方法。出于我的目的,在同一个模块中生成和绑定事件是没有用的。使用根窗口作为旁路是唯一可行的解​​决方案,但相当混乱(需要将根窗口引用传递给每个小部件并在根窗口模块中绑定任何虚拟事件)
    • @voldi:这就是事件的运作方式。 Tkinter 不能让每个小部件都响应每个事件,否则会造成混乱。绑定仅针对特定小部件中的特定事件触发。您的另一个选择是使用 Tkinter 用于它自己的一些小部件的技术 - 有一个绑定到 "all" ,然后绑定函数必须确定事件适用于哪个窗口(例如:对于 &lt;&lt;Copy&gt;&gt;事件它找出哪个小部件具有键盘焦点,并在该机翼上进行复制)。请参阅“实例和类绑定”:effbot.org/tkinterbook/tkinter-events-and-bindings.htm
    【解决方案3】:

    最终我使用了 Bryan 的解决方案并进行了一些改进(我想在模块之间保持一些分离以并行开发它们)。

    总体思路:

    • 添加方法以保存特定虚拟事件的“侦听器”小部件列表

    • 在 root/app 设置期间,使用自定义方法配置小部件之间的“绑定网络”;

    • 为“侦听器”小部件中的特定虚拟事件添加绑定;
    • 当应生成虚拟事件时,'generator' 小部件会为所有注册的带有 when='tail' 参数的“listener”小部件触发事件,以避免立即调用。

    配置绑定网:

    virt_event = '<<open file menu>>'
    
    class mainApp:
        def __init__(self):
            self.root = tk.Tk()
            self.menu = myMenu(self.root)
            self.canvas1 = myCanvas(self.root)
            self.canvas2 = myCanvas(self.root)
            return
    
        ''' some init and setup widgets etc. '''
    
        def root_bindings():
            listeners_list = [self.canvas1, self.canvas2]
            self.menu.add_evt_listeners(virt_event, listeners_list)
            return
    

    在小部件中绑定虚拟事件:

    class myCanvas(tk.Canvas):
        def __init__(self, parent):
            tk.Canvas.__init__(self, parent)
            self._event_bindigs()
            return
    
       def _event_bindings(self):
           self.bind(virt_event, self.on_open_file)
           return
    
       def on_open_file(self, event):
           print('open file event')
           return
    

    向“生成器”小部件添加方法以保存“侦听器”小部件列表:

    class myMenu(tk.Menu):
        def __init__(self, parent):
            tk.Menu.__init__(self, parent)
            self.listeners = {} #dict to keep listeners
            return
    
        def add_event_listeners(self, event, listeners):
            if not isinstance(listeners, list):
                listeners = [listeners]
            if(event in self.events_listeners.keys()):
                self.events_listeners[event] += listeners
            else:
                self.events_listeners[event] = listeners
            return
    
        ''' some menu components setup including internal events'''
    
        def open_file(self, filename):
            ''' some internal handling menu '''
            self.open_file_handler(filename)
    
            ''' broadcast event to registered widgets '''
            for listener in self.event_listeners[virt_event]:
                listener.event_generate(virt_event, when='tail')
            return
    

    【讨论】:

      猜你喜欢
      • 2016-12-27
      • 2017-10-16
      • 1970-01-01
      • 2021-02-22
      • 2017-03-31
      • 2021-07-13
      • 2017-01-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多