在鼠标拖动时激活其他小部件

2 投票
2 回答
2213 浏览
提问于 2025-04-16 19:15

我有好几个Tkinter的标签排成一排,我希望用户能点击并拖动鼠标经过每一个标签,这样就能激活它们。

我知道有绑定这个概念,但我需要在一个绑定里处理多个事件。我试过使用<Button-1><Enter>,但是我需要一个回调函数,只有在这两个条件都满足的时候才会被调用。

我知道l.bind('<Button-1>,<Enter>', ...)这个写法是错误的。

有没有更有Tkinter经验的人知道怎么把绑定连接起来,或者怎么实现多重绑定呢?

2 个回答

0

我今天遇到了同样的问题,感谢@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()
2

解决这个问题的方法是给 ButtonPressButtonRelease 事件绑定一个标志。也就是说,当你按下按钮时,设置一个标记;然后在处理 <Enter>(或者其他事件)时,检查这个标记。

不过,当你按着按钮的时候,是不会收到任何 <Enter> 事件的。这是因为你点击的那个控件会“抓住”鼠标指针,直到你松开按钮为止。在你按着按钮的时候,只有当你进入你最开始点击的那个控件时,才会收到 <Enter> 事件。

所以,建议你改为绑定 <B1-Motion> 事件。这样你就可以使用事件中的 x/y 坐标和 winfo_containing 来判断你现在鼠标指针所在的控件。

不过,试图在一排标签上模拟选择其实工作量很大,但收效甚微。为什么不直接使用一个已经内置选择功能的文本控件呢?你可以调整它的外观,让它看起来像一堆标签(比如:把背景颜色改成和框架一样),而且可以关闭编辑功能。这样可能会更简单一些。

撰写回答