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
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 selectsIndeed. And you don't need to specify a delay of 100 milliseconds. 0
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
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
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.
My issue is solved, but I'm still curious about what is happening here.
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 IYeah. Though for me it was the delay which made it seem fragile.
avoid using it more than I have to because it feels ... a bit
fragile, perhaps.
Am 12.09.23 um 07:43 schrieb John O'Hagan via Python-list:
My issue is solved, but I'm still curious about what is happening here.
MRAB already said it: When you enter the callback function, Tk's
mainloop waits for it to return. So what's happening is:
1. Tk's mainloop pauses
2. temp_unbind() is called
3. TreeviewSelect is unbound
4. events are queued
5. TreeviewSelect is bound again
6. temp_unbind() returns
7. Tk's mainloop continues with the state:
- TreeviewSelect is bound
- events are queued
Am 11.09.23 um 23:58 schrieb Rob Cliffe:
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).
Ah, nice, didn't know that!
I have also found that after() is a cure for some ills, though IYeah. Though for me it was the delay which made it seem fragile. With
avoid using it more than I have to because it feels ... a bit
fragile, perhaps.
a 0 delay, this looks much more reliable.
Am 12.09.23 um 07:43 schrieb John O'Hagan via Python-list:
My issue is solved, but I'm still curious about what is happening
here.
MRAB already said it: When you enter the callback function, Tk's
mainloop waits for it to return. So what's happening is:
1. Tk's mainloop pauses
2. temp_unbind() is called
3. TreeviewSelect is unbound
4. events are queued
5. TreeviewSelect is bound again
6. temp_unbind() returns
7. Tk's mainloop continues with the state:
- TreeviewSelect is bound
- events are queued
[. . .]
FWIW, here's a version without after(), solving this purely on the
python side, not by temporarily unbinding the event, but by
selectively doing nothing in the callback function.
from tkinter import *
from tkinter.ttk import *
class Test:
def __init__(self):
self.inhibit = False
root=Tk()
self.tree = Treeview(root)
self.tree.pack()
self.iid = self.tree.insert('', 0, text='test') Button(root, command=self.temp_inhibit).pack() mainloop()
def callback(self, *e):
if not self.inhibit:
print('called')
def temp_inhibit(self):
self.inhibit = True
self.tree.selection_set(self.iid)
self.tree.selection_remove(self.iid)
self.tree.selection_set(self.iid)
self.inhibit = False
self.callback()
c=Test()
On Tue, 2023-09-12 at 20:51 +0200, Mirko via Python-list wrote:
Am 12.09.23 um 07:43 schrieb John O'Hagan via Python-list:
My issue is solved, but I'm still curious about what is happening
here.
MRAB already said it: When you enter the callback function, Tk's
mainloop waits for it to return. So what's happening is:
1. Tk's mainloop pauses
2. temp_unbind() is called
3. TreeviewSelect is unbound
4. events are queued
5. TreeviewSelect is bound again
6. temp_unbind() returns
7. Tk's mainloop continues with the state:
- TreeviewSelect is bound
- events are queued
[. . .]
Thanks (also to others who have explained), now I get it!
FWIW, here's a version without after(), solving this purely on the
python side, not by temporarily unbinding the event, but by
selectively doing nothing in the callback function.
from tkinter import *
from tkinter.ttk import *
class Test:
def __init__(self):
self.inhibit = False
root=Tk()
self.tree = Treeview(root)
self.tree.pack()
self.iid = self.tree.insert('', 0, text='test')
Button(root, command=self.temp_inhibit).pack()
mainloop()
def callback(self, *e):
if not self.inhibit:
print('called')
def temp_inhibit(self):
self.inhibit = True
self.tree.selection_set(self.iid)
self.tree.selection_remove(self.iid)
self.tree.selection_set(self.iid)
self.inhibit = False
self.callback()
c=Test()
I like this solution better - it's much more obvious to me what it's
doing.
On 2023-09-13 00:40, John O'Hagan via Python-list wrote:
On Tue, 2023-09-12 at 20:51 +0200, Mirko via Python-list wrote:
Am 12.09.23 um 07:43 schrieb John O'Hagan via Python-list:
FWIW, here's a version without after(), solving this purely on
the
python side, not by temporarily unbinding the event, but by
selectively doing nothing in the callback function.
from tkinter import *
from tkinter.ttk import *
class Test:
def __init__(self):
self.inhibit = False
root=Tk()
self.tree = Treeview(root)
self.tree.pack()
self.iid = self.tree.insert('', 0, text='test') Button(root, command=self.temp_inhibit).pack() mainloop()
def callback(self, *e):
if not self.inhibit:
print('called')
def temp_inhibit(self):
self.inhibit = True
self.tree.selection_set(self.iid)
self.tree.selection_remove(self.iid)
self.tree.selection_set(self.iid)
self.inhibit = False
self.callback()
c=Test()
I like this solution better - it's much more obvious to me what
it's
doing.
That code is not binding at all, it's just calling 'temp_inhibit'
when
the button is clicked.
You can remove all uses of self.inhibit and rename 'temp_inhibit' to something more meaningful, like 'delete_item'.
| Sysop: | Keyop |
|---|---|
| Location: | Huddersfield, West Yorkshire, UK |
| Users: | 715 |
| Nodes: | 16 (2 / 14) |
| Uptime: | 32:54:56 |
| Calls: | 12,109 |
| Files: | 15,006 |
| Messages: | 6,518,309 |