0

I am building a bit of a GUI program, one of the features, is that certain buttons are only clickable when appropriate, and are otherwise disabled. The program involves using a mouse, and a ttk Treeview item. What the program does is, when a user clicks within the Treeview, a function is called to determine what they clicked, and things go on from there.

I made my program using something like this, below is a generic example:

treeview.bind('', function_name)

"When a user clicks within the Treeview, run the function", and this worked flawlessly - but there was a very minor delay to things happening (because the mouse de-press was the trigger), so I thought, let's change "ButtonRelease-1" to "ButtonPress-1", so that things happen as soon as the user clicks within the Treeview - and as I predicted, the program is way more "instant" in terms of how it reacts... however there is a big problem created seemingly out of nowhere.

All of my logic stops working when I replace <ButtonRelease-1> with <ButtonPress-1> , I have created a "minimum reproducible sample code" or whatever it's called, which very well demonstrates the problem.

import tkinter as tk
from tkinter import *
from tkinter import ttk

alternate = 1

def add_row():
    global alternate
    if alternate == 0:
        tree.insert('', tk.END,text="L1",values=("Big1","Test12345"))
        alternate = 1
    elif alternate == 1:
        tree.insert('', tk.END,text="L1",values=("Big1","Test"))
        alternate = 0

def delete_row():
    last = tree.get_children()[-1]
    tree.delete(last)
    disablebutton_statecheck()

def disablebutton_statecheck(event=None):                      
    print("triggered statecheck")
    print(list(tree.selection()))
    print(len(tree.selection()))
    if len(tree.selection()) > 0:
        button_6.state(["!disabled"])
    else:
        button_6.state(["disabled"])
    
        

window = Tk()
window.title("test window")
window.geometry("600x600+0+0")
section_1 = Frame(window, width = 600, height = 600, bg="#faf")
section_1.place(x=0, y=0)

tabControl_1 = ttk.Notebook(section_1, height = 470, width = 395) #395
tab1 = ttk.Frame(tabControl_1)

tabControl_1.add(tab1, text='Devices')
tabControl_1.place(y=10,x=25)

tree = ttk.Treeview(tab1, height=7)
tree.place(x=30, y=95)
tree.bind('<Motion>', 'break')

tree["columns"] = ("one", "two")
tree['show'] = 'headings'
tree.column("one", width=100, anchor='c', stretch=False)
tree.column("two", width=100, anchor='c', stretch=False)
tree.heading("one", text="Account")
tree.heading("two", text="Type")
tree.insert("",'end', text="L1",values=("Test","Test12345"))

button_1 = ttk.Button(tab1, text="add a row", command=add_row, width=17)
button_1.place(x=50, y=360)

button_2 = ttk.Button(tab1, text="delete row", command=delete_row, width=17)
button_2.place(x=200, y=360)

button_6 = ttk.Button(tab1, text="disable", width=17)               
button_6.place(x=50, y= 50)                                        

tree.bind('<ButtonRelease-1>', disablebutton_statecheck)              
disablebutton_statecheck()                                          

window.resizable(False,False)
window.mainloop()

Now, with this code, please run as is - and you can see, that when there are no items in the Treeview selected, the disable button is greyed out, but when there is 1 or more items selected in the treeview, the disable button is not greyed out. On windows, ctrl+click will allow to select multiple items, and even to de-select items, so you can play around with things that way. The print statements confirm that after a click event, the program is able to accurately report how many items are selected, and which items they are, as well. Note that treeview row IDs start with I001, not I000.

Next, please replace "Release" with "Press" inside the tree.bind thingy, and run the code again, the behaviour is not correct - now, if you generate a few items, and click between them, you'll notice that the program reports the items previously selected, instead of the items you just selected.

For example, add a few rows - click on row 1, the program reports that nothing is selected, then, click on row 2 (which de-selects row 1), the program then reports that there is 1 item selected, and that it is row 1, despite row 2 being selected, etc, etc.

I cannot understand or explain why "tree.selection()" is not reporting correctly all of a sudden, when changing ButtonRelease-1 to ButtonPress-1 - there must be something I can do to get it to report correctly, as this will improve the "feel" of my program a lot.

Thanks in advance to anyone that reads this!

(OS: Windows 7, Python ver: 3.6.0)

Jones659
  • 49
  • 1
  • 7
  • It is because the actual selection is done after the callback for `` returns. – acw1668 Dec 15 '22 at 01:40
  • Ok... is there any way to impact this behaviour, or to stagger/delay this "callback" until just after the selection is done? If this would be possible, I believe it would still be much better than using ButtonRelease-1 – Jones659 Dec 15 '22 at 01:45

1 Answers1

1

It is because the actual selection is done after the callback for <ButtonPress-1> returns.

However you can delay the execution of disablebutton_statecheck() a bit by using .after() as below:

tree.bind('<ButtonPress-1>', lambda e: window.after(1, disablebutton_statecheck))

Then the default selection task is done before the execution of disablebutton_statecheck().

acw1668
  • 40,144
  • 5
  • 22
  • 34
  • Ah, you posted an answer! This looks interesting, I have seen the "lambda" stuff before, without knowing what it is. I must go to bed, but will try this tomorrow, and see how it goes. If this works it would be revolutionary to my little program – Jones659 Dec 15 '22 at 01:51
  • `lambda` is just a function without name. – acw1668 Dec 15 '22 at 01:53
  • Hey man, just wanted to let you know that this totally worked! I did a little bit of research, seems that the "1" is the delay specified in milliseconds, which is as good as instant for me! – Jones659 Dec 16 '22 at 00:36