I have a tkinter Canvas where some objects are placed and can be moved by the mouse pointer. One of these objects is a text, which is not implemented as canvas-text-item but as a text-widget in a canvas-window-item, as I want to implement syntax highlighting for the text. I know how to implement the move-feature for a canvas-text-item but I am not able to implement the same feature for a canvas-window-item. I believe the reason is that when the mouse pointer is inside the canvas-window-item, it not inside the canvas anymore. This is my example code, where moving text1 works, but moving text2 fails:
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()
As a workaround I implemented a binding of Button-1 directly to the canvas, where I check if there is any canvas-item near the mouse-pointer and if yes this canvas item is moved. But then the user has to click "near" the object to move and not "at" the object to move, which is bad user experience.
You can simply bind <B1-Motion>
on the text widget instead:
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))