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