Tkinter ttk Treeview binding responds to past events!

John O'Hagan research at johnohagan.com
Tue Sep 12 01:43:11 EDT 2023


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.

Regards

John



More information about the Python-list mailing list