Tkinter ttk Treeview binding responds to past events!

MRAB python at mrabarnett.plus.com
Tue Sep 12 10:59:42 EDT 2023


On 2023-09-12 06:43, John O'Hagan via Python-list wrote:
> On Mon, 2023-09-11 at 22:25 +0200, Mirko via Python-list wrote:
>> Am 11.09.23 um 14:30 schrieb John O'Hagan via Python-list:
>> > I was surprised that the code below prints 'called' three times.
>> > 
>> > 
>> > from tkinter import *
>> > from tkinter.ttk import *
>> > 
>> > root=Tk()
>> > 
>> > def callback(*e):
>> >      print('called')
>> > 
>> > tree = Treeview(root)
>> > tree.pack()
>> > 
>> > iid = tree.insert('', 0, text='test')
>> > 
>> > tree.selection_set(iid)
>> > tree.selection_remove(iid)
>> > tree.selection_set(iid)
>> > 
>> > tree.bind('<<TreeviewSelect>>', callback)
>> > 
>> > mainloop()
>> > 
>> > In other words, selection events that occurred _before_ the
>> > callback
>> > function was bound to the Treeview selections are triggering the
>> > function upon binding. AFAIK, no other tk widget/binding
>> > combination
>> > behaves this way (although I haven't tried all of them).
>> > 
>> > This was a problem because I wanted to reset the contents of the
>> > Treeview without triggering a relatively expensive bound function,
>> > but
>> > found that temporarily unbinding didn't prevent the calls.
>> > 
>> > I've worked around this by using a regular button-click binding for
>> > selection instead, but I'm curious if anyone can cast any light on
>> > this.
>> > 
>> > Cheers
>> > 
>> > John
>> 
>> 
>> AFAIK (it's been quite some time, since I used Tk/Tkinter):
>> 
>> These selection events are not triggered upon binding, but after the 
>> mainloop has startet. Tk's eventloop is queue-driven, so the 
>> tree.selection_{set,remove}() calls just place the events on the 
>> queue. After that, you setup a callback and when the mainloop 
>> starts, it processes the events from the queue, executing the 
>> registered callback.
>> 
>> I seem to remember, that I solved a similar issue by deferring the 
>> callback installation using root.after().
>> 
>> 
>> from tkinter import *
>> from tkinter.ttk import *
>> 
>> root=Tk()
>> 
>> def callback(*e):
>>      print('called')
>> 
>> tree = Treeview(root)
>> tree.pack()
>> 
>> iid = tree.insert('', 0, text='test')
>> 
>> tree.selection_set(iid)
>> tree.selection_remove(iid)
>> tree.selection_set(iid)
>> 
>> root.after(100, lambda: tree.bind('<<TreeviewSelect>>', callback))
>> 
>> mainloop()
>> 
>> 
>> 
>> This does not print "called" at all after startup (but still selects 
>> the entry), because the callback has not been installed when the 
>> mainloop starts. But any subsequent interaction with the list 
>> (clicking) will print it (since the callback is then setup).
>> 
>> HTH
> 
> 
> Thanks for your reply. However, please see the example below, which is
> more like my actual use-case. The selection events take place when a
> button is pressed, after the mainloop has started but before the
> binding. This also prints 'called' three times.
> 
> from tkinter import *
> from tkinter.ttk import *
> 
> class Test:
> 
>     def __init__(self):
>        root=Tk()
>        self.tree = Treeview(root)
>        self.tree.pack()
>        self.iid = self.tree.insert('', 0, text='test')
>        Button(root, command=self.temp_unbind).pack()
>        mainloop()
> 
>     def callback(self, *e):
>        print('called')
>     
>     def temp_unbind(self):
>        self.tree.unbind('<<TreeviewSelect>>')
>        self.tree.selection_set(self.iid)
>        self.tree.selection_remove(self.iid)
>        self.tree.selection_set(self.iid)
>        self.tree.bind('<<TreeviewSelect>>', self.callback)
>        #self.tree.after(0, lambda: self.tree.bind('<<TreeviewSelect>>',
>        self.callback))
>        
> c=Test()
> 
> It seems the events are still queued, and then processed by a later
> bind?
> 
> However, your solution still works, i.e. replacing the bind call with
> the commented line. This works even with a delay of 0, as suggested in
> Rob Cliffe's reply. Does the call to after clear the event queue
> somehow?
> 
> My issue is solved, but I'm still curious about what is happening here.
> 
Yes, it's still queuing the events.
When an event occurs, it's queued.

So, you unbound and then re-bound the callback in temp_unbind?

Doesn't matter.

All that matters is that on returning from temp_unbind to the main event 
loop, there are events queued and there's a callback registered, so the 
callback is invoked.

Using the .after trick queues an event that will re-bind the callback 
_after_ the previous events have been handled.


More information about the Python-list mailing list