在鼠标拖动时激活其他小部件
我有好几个Tkinter的标签排成一排,我希望用户能点击并拖动鼠标经过每一个标签,这样就能激活它们。
我知道有绑定这个概念,但我需要在一个绑定里处理多个事件。我试过使用<Button-1>
和<Enter>
,但是我需要一个回调函数,只有在这两个条件都满足的时候才会被调用。
我知道l.bind('<Button-1>,<Enter>', ...)
这个写法是错误的。
有没有更有Tkinter经验的人知道怎么把绑定连接起来,或者怎么实现多重绑定呢?
2 个回答
我今天遇到了同样的问题,感谢@Bryan Oakley的回答,我成功写出了一个可用的解决方案。我想分享我的代码,希望能在未来帮助到某个人。
这个例子创建了两个tkinter的树视图(TreeView),并且可以在这两个树之间拖放树项。关键在于将两个树都绑定到B1-motion事件,这样两个树都能对这些事件做出反应。
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class TreeItem:
"""
Keeps a reference to a treeItem together with its parent tree.
"""
def __init__(self, tree, item):
self.tree = tree
self.item = item
self.itemTxt = tree.item(item,"text")
def __str__(self):
"""
Prints 'treename, itemname' upon calling str(TreeItem)
"""
return f'{self.tree}, {self.itemTxt}'
class Mouse(metaclass=Singleton):
"""
Handles treeitem clicking, dragging, dropping and shows feedback messages about them.
"""
def __init__(self, root):
self.root = root
self.clicked_item = None
self.is_dragging = False
self.drag_time = 0
self.current_hovering_widget = None
def OnMouseDown(self, event):
clicked_item = self.get_item_under_mouse(event)
print("You clicked on", str(clicked_item))
self.clicked_item = clicked_item
def OnDrag(self, event):
self.is_dragging = True
self.show_drag_init_msg()
self.show_hovering_item_change_msg(event)
self.drag_time += 1
def OnMouseUp(self, event):
if self.is_dragging:
self.finish_drag()
self.show_drop_msg()
self.clicked_item = None
def finish_drag(self):
self.is_dragging = False
self.drag_time = 0
def show_drag_init_msg(self):
if self.drag_time == 0:
print("You are now dragging item", self.clicked_item.tree, self.clicked_item.itemTxt)
def show_hovering_item_change_msg(self, event):
currently_hovering = self.get_item_under_mouse(event)
if str(self.current_hovering_widget) != str(currently_hovering):
print("Mouse is above", str(currently_hovering))
self.current_hovering_widget = currently_hovering
def show_drop_msg(self):
dragged_item:TreeItem = self.clicked_item
dragged_onto:TreeItem = self.current_hovering_widget
print(f'You dropped {str(dragged_item)} onto {str(dragged_onto)}')
def get_item_under_mouse(self, event):
current_tree = self.root.winfo_containing(event.x_root, event.y_root)
current_tree_item = current_tree.identify("item", event.x, event.y)
return TreeItem(tree=current_tree, item=current_tree_item)
class Tree:
def __init__(self, root, row, col):
self.root: tk.Tk = root
self.create_tree(root, row, col)
def OnDrag(self,event):
Mouse(self.root).OnDrag(event)
def OnMouseDown(self, event):
Mouse(self.root).OnMouseDown(event)
def OnMouseUp(self, event):
Mouse(self.root).OnMouseUp(event)
def create_tree(self, root, row, col):
self.tree = ttk.Treeview(root)
self.tree.heading('#0', text='Departments', anchor='w')
self.tree.grid(row=row, column=col, sticky='nsew')
self.add_dummy_data()
# add bindings
self.tree.bind("<ButtonPress-1>", self.OnMouseDown)
self.tree.bind("<ButtonRelease-1>", self.OnMouseUp)
self.tree.bind("<B1-Motion>", self.OnDrag)
def add_dummy_data(self):
# adding data
self.tree.insert('', tk.END, text='Administration', iid=0, open=False)
self.tree.insert('', tk.END, text='Logistics', iid=1, open=False)
self.tree.insert('', tk.END, text='Sales', iid=2, open=False)
self.tree.insert('', tk.END, text='Finance', iid=3, open=False)
self.tree.insert('', tk.END, text='IT', iid=4, open=False)
# adding children of first node
self.tree.insert('', tk.END, text='John Doe', iid=5, open=False)
self.tree.insert('', tk.END, text='Jane Doe', iid=6, open=False)
self.tree.move(5, 0, 0)
self.tree.move(6, 0, 1)
root = tk.Tk()
root.geometry('620x200')
# make two trees
tree1 = Tree(root,0,0)
tree2 = Tree(root,0,1)
# run the app
root.mainloop()
解决这个问题的方法是给 ButtonPress
和 ButtonRelease
事件绑定一个标志。也就是说,当你按下按钮时,设置一个标记;然后在处理 <Enter>
(或者其他事件)时,检查这个标记。
不过,当你按着按钮的时候,是不会收到任何 <Enter>
事件的。这是因为你点击的那个控件会“抓住”鼠标指针,直到你松开按钮为止。在你按着按钮的时候,只有当你进入你最开始点击的那个控件时,才会收到 <Enter>
事件。
所以,建议你改为绑定 <B1-Motion>
事件。这样你就可以使用事件中的 x/y 坐标和 winfo_containing
来判断你现在鼠标指针所在的控件。
不过,试图在一排标签上模拟选择其实工作量很大,但收效甚微。为什么不直接使用一个已经内置选择功能的文本控件呢?你可以调整它的外观,让它看起来像一堆标签(比如:把背景颜色改成和框架一样),而且可以关闭编辑功能。这样可能会更简单一些。