提问人:Matthias Schweikart 提问时间:11/16/2023 最后编辑:Matthias Schweikart 更新时间:11/16/2023 访问量:63
如何通过鼠标指针移动 tkinter 画布窗口?
How can I move a tkinter canvas-window by the mouse-pointer?
问:
我有一个 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 直接绑定到画布,在那里我检查鼠标指针附近是否有任何画布项,如果是,则移动此画布项。但是,用户必须单击“靠近”对象进行移动,而不是单击“在”要移动的对象处,这是糟糕的用户体验。
答:
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()
评论