如何通过鼠标指针移动 tkinter 画布窗口?

How can I move a tkinter canvas-window by the mouse-pointer?

提问人:Matthias Schweikart 提问时间:11/16/2023 最后编辑:Matthias Schweikart 更新时间:11/16/2023 访问量:63

问:

我有一个 tkinter Canvas,其中放置了一些对象,可以通过鼠标指针移动。其中一个对象是文本,它不是作为 canvas-text-item 实现的,而是作为 canvas-window-item 中的 text-widget 实现的,因为我想为文本实现语法高亮。我知道如何实现canvas-text-item的move-feature,但我无法为canvas-window-item实现相同的功能。我相信原因是当鼠标指针在画布窗口项内时,它不再在画布内。这是我的示例代码,其中移动 text1 有效,但移动 text2 失败:

    import tkinter as tk
    event_x = None
    event_y = None
    
    def end_move_text(event, item_id):
        canvas_id.tag_unbind(item_id, "<Motion>"         )
        canvas_id.tag_unbind(item_id, "<ButtonRelease-1>")
    
    def move_text(event, item_id):
        global event_x, event_y
        canvas_id.move(item_id, event.x-event_x, event.y-event_y)
        event_x, event_y = event.x, event.y
    
    def move_start_text(event, item_id):
        global event_x, event_y
        event_x, event_y = event.x, event.y
        canvas_id.tag_bind(item_id, "<Motion>"         , lambda event : move_text    (event, item_id))
        canvas_id.tag_bind(item_id, "<ButtonRelease-1>", lambda event : end_move_text(event, item_id))
    
    root = tk.Tk()
    canvas_id = tk.Canvas(root, width=300, height=200)
    
    canvas_text = canvas_id.create_text(50,50, text="text1", tag="text1", font=("Courier", 10))
    canvas_rect = canvas_id.create_rectangle(canvas_id.bbox(canvas_text), outline="black", tag="text1")
    canvas_id.tag_bind(canvas_text, "<Button-1>", lambda event: move_start_text(event, "text1"))
    
    text_widget = tk.Text(canvas_id, height=1, width=5, relief="flat", font=("Courier", 10))
    text_widget.insert("1.0", "text2")
    canvas_wind = canvas_id.create_window(150, 50)
    canvas_id.itemconfig(canvas_wind, window=text_widget)
    canvas_id.tag_bind(canvas_wind, "<Button-1>", lambda event: move_start_text(event, canvas_wind)) # does not work
    
    canvas_id.grid()
    root.mainloop()

作为一种解决方法,我实现了 Button-1 直接绑定到画布,在那里我检查鼠标指针附近是否有任何画布项,如果是,则移动此画布项。但是,用户必须单击“靠近”对象进行移动,而不是单击“在”要移动的对象处,这是糟糕的用户体验。

python tkinter tkinter-canvas

评论


答:

1赞 acw1668 11/16/2023 #1

您可以简单地绑定到文本小部件上:<B1-Motion>

def move_item_window(event, item_id):
    # get the bounding box of the widget
    x1, y1, x2, y2 = canvas_id.bbox(item_id)
    # calculate the center point inside the bounding box
    cx, cy = (x2-x1)/2, (y2-y1)/2
    # move the widget by the distance between the mouse position and the center point
    canvas_id.move(item_id, event.x-cx, event.y-cy)

text_widget.bind("<B1-Motion>", lambda e: move_item_window(e, canvas_wind))

评论

0赞 Matthias Schweikart 11/16/2023
答案按预期工作。但是 cx、cy 不是小部件中心点的画布坐标。cx 是小部件宽度的一半,cy 是小部件高度的一半。但是由于小部件位于具有自己的坐标系(从左上角的 0,0 开始)的canvas_window内,因此 cx 和 cy 可以用作此canvas_window坐标系的坐标。事件坐标也基于canvas_window坐标系,因此它们可用于确定移动的增量。
1赞 Matthias Schweikart 11/16/2023 #2

在阅读了@acw1668的答案后,我为自己的问题添加了答案,因为它包含了一些有价值的想法,使我能够大大简化我的例子。event_y,通过将小部件的中心作为参考点,不再需要跟踪专用的参考点event_x。通过使用“B1-Motion”而不是“Motion”,不再需要动态绑定操作。

import tkinter as tk

def move_item(event, item_id):
    x1, y1, x2, y2 = canvas_id.bbox(item_id)
    if canvas_id.type(item_id)=="window":
        cx, cy = (x2-x1)/2, (y2-y1)/2 # Width and height of item_id calculated from canvas-coordinates
    else:
        cx, cy = (x2+x1)/2, (y2+y1)/2 # Center of item_id in canvas-coordinates
    canvas_id.move(item_id, event.x-cx, event.y-cy)

root = tk.Tk()
canvas_id = tk.Canvas(root, width=300, height=200)

canvas_text = canvas_id.create_text(50,50, text="text1", tag="text1", font=("Courier", 10))
canvas_rect = canvas_id.create_rectangle(canvas_id.bbox(canvas_text), outline="black", tag="text1")
canvas_id.tag_bind(canvas_text, "<B1-Motion>", lambda event: move_item(event, "text1"))

text_widget = tk.Text(canvas_id, height=1, width=5, relief="flat", font=("Courier", 10))
text_widget.insert("1.0", "text2")
canvas_wind = canvas_id.create_window(150, 50)
canvas_id.itemconfig(canvas_wind, window=text_widget)
text_widget.bind("<B1-Motion>", lambda e: move_item(e, canvas_wind))

canvas_id.grid()
root.mainloop()