during writing some simple gui app in tkinter I met some small problem. Let's say I have custom menu widget (derived from tk.Menu) and custom canvas widget (derived from tk.Canvas).
I would like to generate event from menu callback function and invoke it in canvas widget. I need to do it that way because it future I would like to add more widgets which should react to clicked position in the menu.
I tried to do it that way:
custom menu:
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
custom 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
When I click position in menu, _handler callback is invoked properly and event <> is generated, but on_event callback is no invoked. I've tried to add when='tail' parameter, add self.update() etc. but without any result. Anybody knows how to do it?
Here's my sample code for creating custom virtual events. I created this code to simulate calling servers which take a long time to respond with data:
In this code sample, I'm creating custom virtual events that are invoked once a simulated server has completed retrieving data. The event handler, onVirtualEvent, is bound to the custom virtual events at the root level.
Simulated servers will run in a separate thread of execution when the push button is clicked. I'm using a custom decorator, TS_decorator, to create the thread of execution that the call to simulated servers will run in.
The really interesting part about my approach is that I can supply data retrieved from the simulated servers to the event handlers by calling the FireVirtualEvent method. Inside this method, I am adding a custom attribute to the Event class which will hold the data to be transmitted. My event handlers will then extract the data from the servers by using this custom attribute.
Although simple in concept, this sample code also alleviates the problem of GUI elements not updating when dealing with code that takes a long time to execute. Since all worker code is executed in a separate thread of execution, the call to the function is returned from very quickly, which allows GUI elements to update. Please note that I am also passing a reference to the myApp class to the simulated servers so that they can call its FireVirtualEvent method when data is available.
You need to add the binding to the widget that gets the event. In your case you are generating the event on the menu, so you need to bind to the menu.
You could also generate the event on the canvas, and keep the binding on the canvas. Or, associate the event with the root window, and bind to the root window.
A common technique -- employed by tkinter itself in some cases -- is to generate the event on the root window, and then have a single binding on the root window (or for all windows with
bind_all
) for that event. The single binding must then determine which window to affect by some means (often, for example, by getting the window with the keyboard focus).Of course, if you have a way of determining which widget gets the binding, you can use that method at the time you generate the event to generate the event directly on the appropriate widget.
For more information see http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm, specifically the section of that document with the heading "Instance and Class Bindings".
Eventually I used Bryan's solution with some improvements (I wanted to keep some separation between modules to develop them parallelly).
General idea:
add method to save list of 'listener' widgets for particular virtual event
during root/app setup, configure "binding nets" between widgets with custom method;
configure binding nets:
bind virtual events in widgets:
add method to 'generator' widget to save list of 'listener' widgets: