Re: Tkinter ttk Treeview binding responds to past events!
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('<>', 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('<>', 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('<>')
self.tree.selection_set(self.iid)
self.tree.selection_remove(self.iid)
self.tree.selection_set(self.iid)
self.tree.bind('<>', self.callback)
#self.tree.after(0, lambda: self.tree.bind('<>',
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
--
https://mail.python.org/mailman/listinfo/python-list
Re: Tkinter ttk Treeview binding responds to past events!
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('<>', 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('<>', 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('<>')
self.tree.selection_set(self.iid)
self.tree.selection_remove(self.iid)
self.tree.selection_set(self.iid)
self.tree.bind('<>', self.callback)
#self.tree.after(0, lambda: self.tree.bind('<>',
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.
--
https://mail.python.org/mailman/listinfo/python-list
Re: Tkinter ttk Treeview binding responds to past events!
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 I
avoid using it more than I have to because it feels ... a bit
fragile, perhaps.
Yeah. Though for me it was the delay which made it seem fragile.
With a 0 delay, this looks much more reliable.
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()
HTH and regards
--
https://mail.python.org/mailman/listinfo/python-list
Re: Tkinter ttk Treeview binding responds to past events!
On 2023-09-12 19:51, 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 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! Well, strictly speaking, it's the order in which they were queued except for .after, which will be postponed if you specify a positive delay. [snip] -- https://mail.python.org/mailman/listinfo/python-list
Re: Tkinter ttk Treeview binding responds to past events!
On 12/09/2023 19:51, Mirko via Python-list wrote: 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. Yeah. Though for me it was the delay which made it seem fragile. With a 0 delay, this looks much more reliable. At one point I found myself writing, or thinking of writing, this sort of code after(1, DoSomeThing) after(2, Do SomeThingElse) after(3, DoAThirdThing) ... but this just felt wrong (is it reliable if some Things take more than a millisecond? It may well be; I don't know), and error-prone if I want to add some more Things. Rob Cliffe -- https://mail.python.org/mailman/listinfo/python-list
Re: Tkinter ttk Treeview binding responds to past events!
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.
Regards
John
--
https://mail.python.org/mailman/listinfo/python-list
Re: Tkinter ttk Treeview binding responds to past events!
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:
> 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.
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'.
--
https://mail.python.org/mailman/listinfo/python-list
Re: Tkinter ttk Treeview binding responds to past events!
On Wed, 2023-09-13 at 01:33 +0100, MRAB via Python-list wrote:
> 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'.
You're right of course, not as obvious as I thought!
--
https://mail.python.org/mailman/listinfo/python-list
