Re: Tkinter ttk Treeview binding responds to past events!

2023-09-12 Thread John O'Hagan via Python-list
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!

2023-09-12 Thread MRAB via Python-list

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!

2023-09-12 Thread Mirko via Python-list

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!

2023-09-12 Thread MRAB via Python-list

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!

2023-09-12 Thread Rob Cliffe via Python-list



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!

2023-09-12 Thread John O'Hagan via Python-list
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!

2023-09-12 Thread MRAB via Python-list

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!

2023-09-12 Thread John O'Hagan via Python-list
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