Tkinter ttk Treeview binding responds to past events!

Rob Cliffe rob.cliffe at btinternet.com
Mon Sep 11 17:58:59 EDT 2023



On 11/09/2023 21:25, 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
Indeed.  And you don't need to specify a delay of 100 milliseconds. 0 
will work (I'm guessing that's because queued actions are performed in 
the order that they were queued).
I have also found that after() is a cure for some ills, though I avoid 
using it more than I have to because it feels ... a bit fragile, perhaps.
E.g. suppose the mouse is clicked on a widget and tk responds by giving 
that widget the focus, but I don't want that to happen.
I can't AFAIK prevent the focus change, but I can immediately cancel it with
     X.after(0, SomeOtherWidget.focus_set)
where X is any convenient object with the "after" method (just about any 
widget, or the root).
Best wishes
Rob Cliffe



More information about the Python-list mailing list