Python Tkinter在不同的小部件之间生成和调用虚拟事件

Python Tkinter在不同的小部件之间生成和调用虚拟事件,python,events,tkinter,virtual,Python,Events,Tkinter,Virtual,在tkinter中编写一些简单的gui应用程序时,我遇到了一些小问题。假设我有自定义菜单小部件(源自tk.menu)和自定义画布小部件(源自tk.canvas) 我想从菜单回调函数生成事件,并在画布小部件中调用它。我需要这样做,因为我想添加更多的小部件,这些小部件应该对菜单中单击的位置做出反应 我试着这样做: 自定义菜单: class MainMenu(tk.Menu): def __init__(self, parent): tk.Menu.__init__(self,

在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主菜单(tk.Menu):
定义初始化(自身,父级):
tk.Menu.\uuuuu init\uuuuuuuu(自,父)
self.add\u comand(label='foo',self.\u处理程序)
返回
def_处理程序(自身,*args):
打印(“处理菜单”)
自我事件_生成(“”)
返回
自定义画布:

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
类CustomCanvas(tk.Canvas):
def uuu init_uuuu(self,parent,name=''):
tk.Canvas.\uuuuu init\uuuuuu(自,父)
self.bind(“”,self.on_事件)
返回
def on_事件(自身、事件):
打印(事件)
返回

当我在菜单中单击“位置”时,会正确调用\u处理程序回调并生成事件,但不会调用on\u事件回调。我尝试添加when='tail'参数,添加self.update()等,但没有任何结果。有人知道怎么做吗?

您需要向获取事件的小部件添加绑定。在本例中,您正在菜单上生成事件,因此需要绑定到菜单

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

tkinter本身在某些情况下使用的一种常见技术是在根窗口上生成事件,然后在根窗口上为该事件(或所有带有
bind\u all
的窗口)进行单个绑定。然后,单个绑定必须通过某种方式(通常,例如,通过键盘焦点获取窗口)确定要影响的窗口

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


有关更多信息,请参见文档中标题为“实例和类绑定”的部分。

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

总体思路:

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

  • 在根/应用程序设置期间,使用自定义方法配置小部件之间的“绑定网络”

  • 在“侦听器”窗口小部件中为特定虚拟事件添加绑定
  • 当应该生成虚拟事件时,“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 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

下面是创建自定义虚拟事件的示例代码。我创建此代码是为了模拟呼叫服务器,这些服务器需要很长时间才能响应数据:

#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()
#自定义虚拟事件
尝试:
从Tkinter进口*
导入tkMessageBox
除恐怖外:
尝试:
从tkinter进口*
从tkinter导入消息框
除例外情况外:
通过
导入时间
从线程导入线程
VirtualEvents=[“”,“”]
def TS_装饰器(功能):
def存根(*args,**kwargs):
func(*args,**kwargs)
def挂钩(*args,**kwargs):
线程(target=stub,args=args).start()
回程钩
myApp类:
定义初始化(自):
self.root=Tk()
self.makeWidgets(self.root)
self.makeVirtualEvents()
self.state=False
self.root.mainloop()
def makeWidgets(自身、父级):
self.lbl=标签(父项)
self.lbl.pack()
按钮(parent,text=“Get Data”,command=self.getData).pack()
def onVirtualEvent(自身、事件):
打印(“虚拟事件数据:{}”。格式(Event.VirtualEventData))
self.lbl.config(text=event.VirtualEventData)
def makeVirtualEvents(自):
对于VirtualEvents中的e:
如果需要,self.root.event_add(e,'None')#可以在这里添加一个触发器序列来代替'None'
self.root.bind(e,self.onVirtualEvent,“%d”)
def FireVirtualEvent(自身、vEvent、数据):
Event.VirtualEventData=数据
self.root.event_generate(vEvent)
def getData(自):
如果不是自我状态:
虚拟服务器(自身)
其他:
pooserver(self)
self.state=非self.state
@装饰工
def虚拟服务器(m):
时间。睡眠(3)
m、 FireVirtualEvent(VirtualEvents[0],“来自虚拟服务器的您好”)
@装饰工
def POOPOOS服务器(m):
时间。睡眠(3)
m、 FireVirtualEvent(VirtualEvents[1],“来自Poo Poo服务器的您好”)
如果名称=“\uuuuu main\uuuuuuuu”:
app=myApp()
在这个代码示例中,我创建了自定义虚拟事件,一旦模拟服务器完成数据检索,就会调用这些事件。事件处理程序onVirtualEvent绑定到根级别的自定义虚拟事件

单击按钮时,模拟服务器将在单独的执行线程中运行。我正在使用一个定制的decorator,TS_decorator,来创建对模拟服务器的调用将在其中运行的执行线程

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

虽然在概念上很简单,但此示例代码还缓解了处理tak的代码时GUI元素不更新的问题
#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()