我正在编写一个程序,它使用 ttk.Treeview 小部件并且还有一个 tk.scale 滑块小部件。 我想将左右箭头键事件绑定到滑块小部件,这有效,但我发现 Treeview 小部件也自动绑定到这些键。
当我尝试用箭头移动我的滑块时,我发现我也在树视图对象中导航。
我已经尝试从我周围的 Tk 类以及 treeview 小部件本身取消绑定键,没有任何变化(我什至在 treeview open_node 回调中运行取消绑定,没有任何变化)。
这可能是 Tcl/Tk 的一个特性,不能在 Python 中受到影响,但我想看看是否可以修复。
我正在使用 Python 3.10
除了尝试解除绑定之外,我还创建了一个最小化的程序来说明问题。我的兴趣是打破箭头键与树视图的绑定——而不是因为这个例子没有完全填充树而出现的结果异常。
关键绑定是问题!
如果有任何见解,我将不胜感激。
""" Minimal example to illustrate hidden binding of arrow keys to ttk.Treeview
To see problem: run program, double click to expand 'top_node'
then click on 'leaf_node' and hit right arrow key
Program generates a Tcl Exception: cannot delete root item as a result of
trying to open a leaf-node.
But the issue is that I can find no way to unbind the arrow keys to use them
for another purpose.
"""
import tkinter as tk
from tkinter import ttk
class MinimalTree(tk.Tk):
def __init__(self):
super().__init__()
self.tree = ttk.Treeview(self, height=25, takefocus=False)
self.tree.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.nodes = dict()
self.insert_node('', 'top_node', 'top_node')
self.tree.bind('<<TreeviewOpen>>', self.open_node)
self.unbind_now()
def insert_node(self, parent, text, path):
node = self.tree.insert(parent, 'end', text=text, open=False)
self.nodes[node] = path
if path == 'top_node':
self.tree.insert(node, 'end')
self.unbind_now()
def open_node(self, event):
print(f'open_node {event}')
node = self.tree.focus()
abspath = self.nodes.pop(node, None)
if abspath:
self.tree.delete(self.tree.get_children(node))
self.insert_node(node, 'leaf_node', 'top_node/leaf_node')
self.unbind_now()
def unbind_now(self): # doesn't help: '<Right>' still calls open_node
self.tree.unbind('<Left>')
self.tree.unbind('<Right>')
self.unbind('<Left>')
self.unbind('<Right>')
app = MinimalTree()
app.mainloop()
下面是使用 acw1668 评论中的建议并添加滑块的代码更新。
使用此代码,一旦树打开,树视图和缩放滑块都不会收到左右键(尽管在树视图树第一次打开之前滑块确实收到按下的任何左箭头或右箭头)。
但是,如果我用“break”注释掉两个绑定语句,则滑块和树视图都会收到相同的击键。
这似乎违反了只有一个小部件接收按键事件的规定。
如果这无法解决,那么我会看到两个回退 - 一个是对滑块使用不同的键(不是最佳的),另一个是将树视图放在它自己的顶级窗口中,这将强制分配键盘焦点(但对用户来说很笨拙)。
带滑块的代码:
import tkinter as tk
from tkinter import ttk
class MinimalTree(tk.Tk):
def __init__(self):
super().__init__()
self.tree = ttk.Treeview(self, height=25, takefocus=False)
self.tree.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.nodes = dict()
self.insert_node('', 'top_node', 'top_node')
self.tree.bind('<<TreeviewOpen>>', self.open_node)
# fix from acw1668's comment on Stackoverflow
# which does block the left and right arrow event on the tree:
self.tree.bind('<Left>', lambda e: 'break')
self.tree.bind('<Right>', lambda e: 'break')
# adding a Scale widget with arrow key focus
# but I find the 'break' bindings above keep these new bindings from working
# If I remove the two 'break' bindings, then both widget get the arrow keys,
# but if I included the breaks, neither widget gets the keys
# how to allow the Scale to bind to the arrows but break the binding of the treeview?
self.scale = tk.Scale(self, orient=tk.HORIZONTAL, width=20, command=self.scroll, showvalue=0)
self.scale.pack(fill=tk.X)
self.bind('<Left>', lambda e: self.scale.set(self.scale.get() - 1))
self.bind('<Right>', lambda e: self.scale.set(self.scale.get() + 1))
def scroll(self, event):
print(f'scale scrolled to {self.scale.get()}')
# self.bind_to_scale()
def insert_node(self, parent, text, path):
node = self.tree.insert(parent, 'end', text=text, open=False)
self.nodes[node] = path
if path == 'top_node':
self.tree.insert(node, 'end')
def open_node(self, event):
node = self.tree.focus()
abspath = self.nodes.pop(node, None)
if abspath:
self.tree.delete(self.tree.get_children(node))
for i in range(5):
name = f'leaf node {i}'
self.insert_node(node, 'name', f'top_node/{name}')
app = MinimalTree() app.mainloop()
为了不触发默认绑定,您需要将事件绑定到返回字符串
"break"
的函数。如果你这样做,事件的所有进一步处理都会停止。
您对
unbind
的调用不起作用,因为默认绑定位于小部件的内部类而不是小部件本身或根窗口。小部件本身没有 <Left>
和 <Right>
的绑定,所以 unbind
没有什么可以删除的。