简体   繁体   English

ttk的回调时间 treeview

[英]Callback timing with ttk treeview

I have a ttk treeview populated with items, I have some buttons next to the treeview, and the state of those buttons is determined by what is highlighted within the treeview (for example, if no items are currently highlighted, all option buttons are disabled, if there is one or more items selected/highlighted, and their state is enabled, the "disable" button will become clickable), it's quite simple.我有一个 ttk treeview 填充了项目,我在 treeview 旁边有一些按钮,这些按钮的 state 由 treeview 中突出显示的内容决定(例如,如果当前没有突出显示的项目,则所有选项按钮都被禁用,如果选择/突出显示了一个或多个项目,并且启用了它们的 state,则“禁用”按钮将变为可点击),这非常简单。

The task of querying the treeview to find out what is selected, and assigning the state of each button accordingly, is handled by a "checker" function. Because items in the treeview can only become highlighted or un-highlighted by user input (such as a mouse click), this "checker" function only needs to run once after every user input inside the treeview area.查询 treeview 以找出选择的内容,并相应地分配每个按钮的 state 的任务由“检查器”function 处理。因为 treeview 中的项目只能通过用户输入(例如单击鼠标),此“检查器”function 只需在 treeview 区域内的每个用户输入后运行一次。

So, I bound <ButtonPress-1> inside of the treeview, to run the "checker" function. The issue I originally ran into, is that the user would click inside the treeview, but the function would run before the "selection" was updated inside the treeview, so the checker function wouldn't actually see that a user had clicked on a treeview item the first time around.因此,我将<ButtonPress-1>绑定到 treeview 内部,以运行“检查器”function。我最初遇到的问题是,用户会在 treeview 内部单击,但 function 会在“选择”之前运行在 treeview 内部更新,因此检查器 function 实际上不会看到用户第一次点击了 treeview 项目。

In a previous question ( "ButtonPress" not working whilst "ButtonRelease" does ), this issue was fixed by replacing my "bind" line with this paraphrased line of code:在上一个问题中( “ButtonPress”不工作而“ButtonRelease”不工作),这个问题是通过用这个解释的代码行替换我的“绑定”行来解决的:

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

The above line ensured that after an LMB click occured, the treeview selection would update, then the checker function would run, and update the state of the buttons in accordance to the treeview selection.上面的代码确保了在点击 LMB 之后,treeview 选项会更新,然后检查器 function 会运行,并根据 treeview 选项更新按钮的 state。 Everything worked "instantly", it was really cool.一切都“立即”工作,真的很酷。

Now there has arisen aa need to pass additional context to the checker function, namely, what it was called by.现在需要将额外的上下文传递给检查器 function,即它被调用的内容。 I chose to do this by using a string, and then modifying the checker function, so that it does different things depending on what string it receives.我选择通过使用字符串来执行此操作,然后修改检查器 function,以便它根据接收到的字符串执行不同的操作。

I will show two bits of code, one demonstrating how things should work, and one showing what happens when I try to pass things to the function. The checker function is called "disablebutton_statecheck".我将展示两段代码,一段展示事情应该如何工作,另一段展示当我尝试将事情传递给 function 时会发生什么。检查器 function 称为“disablebutton_statecheck”。

First code (working)第一个代码(工作)

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

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)
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_6 = ttk.Button(tab1, text="disable", width=17)               
button_6.place(x=50, y= 50)                                        

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

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

When this runs, the following is printed to the shell:运行时,以下内容将打印到 shell:

triggered statecheck
[]
0

When the function is triggered, it prints as such, whilst the 2nd line shows the selected items in the treeview, and the 3rd line tells you how many items are selected.当 function 被触发时,它会这样打印,而第二行显示 treeview 中的所选项目,第三行告诉您有多少项目被选中。

With the program running, click once on the only item in the treeview using left click of a mouse, the following is then printed to shell:在程序运行的情况下,用鼠标左键单击 treeview 中的唯一项,然后将以下内容打印到 shell:

triggered statecheck
['I001']
1

This is all correct!这一切都是正确的!

Now, I have modified the code, to receive extra context from wherever it is called, and now, it's not working.现在,我已经修改了代码,以便从调用它的任何地方接收额外的上下文,但现在它不起作用了。

Here is the modified code, every line which is new or different has been noted:这是修改后的代码,每一行是新的或不同的都已注明:

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

def disablebutton_statecheck(y):                                    ###amended                   
    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"])
    if y == "system":                                               ###new
        print("this command was run by the system")                 ###new
    elif y == "click":                                              ###new
        print("this command came from user mouse click")            ###new
    
        

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)
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_6 = ttk.Button(tab1, text="disable", width=17)               
button_6.place(x=50, y= 50)                                        

tree.bind('<ButtonPress-1>', lambda e: window.after(1, disablebutton_statecheck("click")))  ###amended        
disablebutton_statecheck("system")                                                          ###amended                                       

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

When the program runs, the correct thing is printed to the shell:当程序运行时,正确的东西被打印到 shell:

triggered statecheck
[]
0
this command was run by the system

But if you click once on the item in the treeview, the shell gives strange result:但是如果你点击一次 treeview 中的项目,shell 会给出奇怪的结果:

triggered statecheck
[]
0
this command came from user mouse click

What the heck?有没有搞错? That bracket should have contained 'I001', and the zero should have been a one.该括号应该包含“I001”,零应该是一个。 You need to click a second time on the treeview item, to receive the correct result:您需要再次单击 treeview 项目,才能收到正确的结果:

triggered statecheck
['I001']
1
this command came from user mouse click

Can someone explain what's going on here, and how I can fix it?有人可以解释这里发生了什么,我该如何解决? Thanks in advance, apologies this post is so damn long.在此先感谢,抱歉这篇文章太长了。

When you add a binding to a widget, that binding happens before the bindings on the widget class. It is the bindings on the widget class that cause the selection to change.当您向小部件添加绑定时,该绑定发生在小部件 class 上的绑定之前。正是小部件 class 上的绑定导致选择更改。

Instead of using after , a better solution is to bind to "<<TreeviewSelect>>" .除了使用after ,更好的解决方案是绑定到"<<TreeviewSelect>>" This will fire after the selection changes regardless of how it changes.这将在选择更改后触发,无论它如何更改。

tree.bind('<<TreeviewSelect>>', lambda event: disablebutton_statecheck("click"))

For a description of how bindings work, see this answer to the question Basic query regarding bindtags in tkinter .有关绑定如何工作的描述,请参阅tkinter 中关于绑定标签的基本查询问题的回答 This answer uses the Entry widget as an example, but the mechanism is the same for all widgets.该答案以Entry小部件为例,但所有小部件的机制都是相同的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM