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

时间:2015-08-04 00:12:08

标签: python events tkinter virtual

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

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

我试着这样做:

自定义菜单:

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

自定义画布:

<appSettings>
    <add key="RedisConnection" value="cachename.redis.cache.windows.net,ssl=true,password=password"/>
  </appSettings>
    <sessionState mode="Custom" customProvider="MySessionStateStore">
      <providers>
        <add name="MySessionStateStore" 
             type="Microsoft.Web.Redis.RedisSessionStateProvider" connectionString="RedisConnection"/>
      </providers>
    </sessionState>

当我点击菜单中的位置时,会正确调用_handler回调并且事件&lt;&gt;生成,但没有调用on_event回调。我试图添加=&#39; tail&#39;参数,添加self.update()等但没有任何结果。有谁知道怎么做?

3 个答案:

答案 0 :(得分:3)

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

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

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

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

有关详细信息,请参阅http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm,特别是该文档中带有标题&#34;实例和类绑定&#34;的部分。

答案 1 :(得分:1)

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

总体思路:

  • 添加保存&#39;听众列表的方法&#39;特定虚拟事件的小部件

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

  • 为“听众”中的特定虚拟事件添加绑定&#39;窗口小部件;
  • 当应该生成虚拟事件时,&#39;生成器&#39;小部件为所有已注册的&#34;听众激活事件&#34;小时候的小部件=&#39; tail&#39;参数,以避免立即调用。

配置绑定网:

RelativeLayout.LayoutParams params = 
    new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
        RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
textView.setLayoutParams(params);

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

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

将方法添加到&#39;生成器&#39;用于保存“聆听者”列表的小部件窗口小部件:

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

答案 2 :(得分: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方法。