Python/tkinter/canvas:鼠标按下+拖动+鼠标按下事件仅返回鼠标按下项目标签?

问题描述 投票:0回答:2

在 Python/tkinter/canvas 上:用户将单击一个项目,拖动到另一个项目并释放。项目本身不会被拖动/移动;我只需要这两个项目的标签。我观察到的问题是,当鼠标按下一个项目,然后拖动到另一个项目进行鼠标释放时,画布返回第一个项目 id#/标签作为事件 id#/标签。

鼠标坐标可以正确报告这两个事件,并且标签在其他方面都可以正常工作。

这是一个错误还是实施限制?有什么解决方法吗?还是我做错了?

下面的演示代码也位于 https://pastebin.com/MD9AuAUS

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
from functools import partial


'''
Ctag Tester v1.0
29 February 2020
Written for Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:06:47) 
[MSC v.1914 32 bit (Intel)] on win32 / Windows 10 / VS Code (ide)

This is a demo of a tkinter / canvas bug. Or something. (?)

1. Each of the two squares on the canvas should report its tag 
for a mouse-1-down and mouse-1-up event.

2. It always reports properly if you click and release on the same item.

3. PROBLEM: If you click down on one square and then drag to the other
square, the mouse-up event gives the same tag as the mouse-down event,
even though they did not occur on the same item.

4. The mouse x-y coordinates are also reported, and those always
report correctly wherever those two events occur.

5. What I assumed should happen is that the mouse-down event should get
the tag of the item that it occurs on, and the mouse-up event should get
the tag of the item that it occurs on.
'''


def main():
    program = TestProgram1()
    program.window.mainloop()

class TestProgram1:

    def __init__(self):
        self.window = tk.Tk()
        self.window.title( "Ctag Tester v1.0" )
        self.window_width = 800
        self.window_height = 600
        self.window.minsize(self.window_width, self.window_height)
        self.window_position_x = 40
        self.window_position_y = 40
        self.window.geometry('%dx%d+%d+%d' % (self.window_width, self.window_height, self.window_position_x, self.window_position_y))
        self.window.resizable(False, False)



        x_pos = 20
        y_pos = 20
        self.staticlabel_38 = tk.Label(master=self.window, text="mousedown xy:")
        self.staticlabel_38.place(x=x_pos, y=y_pos)
        x_pos = 180
        y_pos = 20
        self.staticlabel_42 = tk.Label(master=self.window, text="mouseup xy:")
        self.staticlabel_42.place(x=x_pos, y=y_pos)

        x_pos = 20
        y_pos = 50
        self.label_mousedown = tk.Label(master=self.window, text="[none]")
        self.label_mousedown.place(x=x_pos, y=y_pos)
        x_pos = 180
        y_pos = 50
        self.label_mouseup = tk.Label(master=self.window, text="[none]")
        self.label_mouseup.place(x=x_pos, y=y_pos)

        x_pos = 20
        y_pos = 100
        self.staticlabel_55 = tk.Label(master=self.window, text="mousedown tag:")
        self.staticlabel_55.place(x=x_pos, y=y_pos)
        x_pos = 180
        y_pos = 100
        self.staticlabel_59 = tk.Label(master=self.window, text="mouseup tag:")
        self.staticlabel_59.place(x=x_pos, y=y_pos)

        x_pos = 20
        y_pos = 130
        self.label_tagdown = tk.Label(master=self.window, text="[none]")
        self.label_tagdown.place(x=x_pos, y=y_pos)
        x_pos = 180
        y_pos = 130
        self.label_tagup = tk.Label(master=self.window, text="[none]")
        self.label_tagup.place(x=x_pos, y=y_pos)

        x_pos = 350
        y_pos = 40
        self.canvas1 = tk.Canvas(master=self.window, width=400, height=400)
        self.canvas1.place(x=x_pos, y=y_pos)
        self.canvas1.create_rectangle(2, 2, 400, 400, outline='black', fill='white')

        target_1 = self.canvas1.create_rectangle(80, 80, 180, 180, outline='black', fill='green', tags=('greenbox'))
        self.canvas1.tag_bind('greenbox', '<Button-1>', partial(self.chart_item_mousedown_event, target_1))
        self.canvas1.tag_bind('greenbox', '<ButtonRelease-1>', partial(self.chart_item_mouseup_event, target_1))      

        target_2 = self.canvas1.create_rectangle(240, 180, 340, 280, outline='black', fill='red', tags=('redbox'))
        self.canvas1.tag_bind('redbox', '<Button-1>', partial(self.chart_item_mousedown_event, target_2))
        self.canvas1.tag_bind('redbox', '<ButtonRelease-1>', partial(self.chart_item_mouseup_event, target_2))



    def chart_item_mousedown_event(self, c_handle, event):
        temp_tag = self.canvas1.gettags(c_handle)
        if temp_tag == "":
            self.label_tagdown.config(text='[none]')
        else:
            self.label_tagdown.config(text=temp_tag)
        m_event = "x:" + str(event.x) + ", y:" + str(event.y)
        self.label_mousedown.config(text=m_event)



    def chart_item_mouseup_event(self, c_handle, event):
        temp_tag = self.canvas1.gettags(c_handle)
        if temp_tag == "":
            self.label_tagup.config(text='[none]')
        else:
            self.label_tagup.config(text=temp_tag)
        m_event = "x:" + str(event.x) + ", y:" + str(event.y)
        self.label_mouseup.config(text=m_event)



if __name__ == "__main__":
    main()
python tkinter tkinter-canvas
2个回答
2
投票

这不是一个错误,也不是一个真正的限制。这就是 tkinter 的设计原理。获取按钮按下事件的小部件和/或画布项目将接收后续释放事件。

您需要让绑定到释放事件的代码找到光标下方的画布项。

例如,给定一个事件对象,以下是如何找到最接近指针的画布项:

canvas = event.widget
canvasx = canvas.canvasx(event.x)
canvasy = canvas.canvasy(event.y)
item = canvas.find_closest(canvasx, canvasy)

0
投票

正如 @Bryan Oakley 所说,您可以通过巧妙地利用拖动时 drag-end-event (鼠标释放)由 drag-start-component 接收这一事实,在 tkinter 中实现拖放行为,甚至如果当鼠标悬停在 drag-end-component 上时释放鼠标。

拖结束事件中:

  • 我们可以通过获取事件父级来获取drop-start-component(因为该事件仅由启动组件接收)
  • 我们可以使用鼠标的绝对位置来获取drop-end-component,就像@Bryan Oakley所说。
import tkinter as tk

def button_released(event: tk.Event):
    drag_from = event.widget
    drag_to = event.widget.winfo_containing(event.x_root, event.y_root)
    if isinstance(drag_to, tk.Frame):
        print(f"Dragged from Frame {frames.index(drag_from)} to Frame {frames.index(drag_to)}")
    else:
        print("Drag cancelled")

root = tk.Tk()
root.geometry("350x350")

frames = []
for i in range(2):
    for j in range(2):
        frame = tk.Frame(root, width=150, height=150, bg="lightblue")
        frame.grid(row=i, column=j, padx=10, pady=10)
        frame.bind("<ButtonRelease-1>", button_released)
        frames.append(frame)

root.mainloop()
© www.soinside.com 2019 - 2024. All rights reserved.