Tkinter ttk Treeview binding responds to past events!
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 -- https://mail.python.org/mailman/listinfo/python-list
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 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 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
SOLVED: Tkinter button-motion event behaves differently under Windows and Linux
I'm posting this in case anyone else encounters the same problem, and to ask for suggestions, if any, about a better way to do it. I'm implementing a method for dragging embedded widgets on a Text widget. When the left mouse button is held down over an embedded widget and the mouse is dragged across other widgets embedded in the same parent, the original widget moves to the new positions until the button is released. Here's some minimal code: --- from tkinter import * text = Text(Tk()) text.pack() def drag(e): print(e.widget) target = e.widget.winfo_containing(e.x_root, e.y_root) if target and target not in (text, e.widget): if text.compare(target, '>', e.widget): target = f'{target} + 1 char' text.window_create(target, window=e.widget) for n in ('1', '2', '3'): l=Label(text, text=n, width=10) #l.bind('', lambda e:e.widget.grab_set()) l.bind('', drag) #l.bind('', lambda e:e.widget.grab_release()) text.window_create(END, window=l) mainloop() --- This works as intended for me on Windows (11). The print statement always shows the name of the first selected widget while the button is held down, regardless of where the mouse is dragged to. But on Linux (Debian testing with Gnome), for me the above code only moves the widget to the first new position it is dragged to, any further dragging is ineffective, and the print statement shows the names of the subsequently-traversed widgets. There is a further issue in the real code (not shown here) also on Linux only, where if the cursor traverses any other widgets bound to '', they are also triggered, which is not intended. Just a guess, but it seems that on Linux, the focus switches to whatever widget is under the cursor even during dragging, so any bindings on the originally-clicked widget are no longer triggered, whereas Windows maintains focus on the originally-clicked widget during dragging until the button is released. If that's the case (and I never thought I'd say this), I think Windows is right! But for all I know it might be the window manager or something else. I eventually figured out that the commented lines calling grab_set and grab_release solved the issue for me on Linux. I haven't found this documented anywhere and I'm interested to know if anyone can cast any light on it. Thanks -- https://mail.python.org/mailman/listinfo/python-list