pythontkintermouseeventtext-widget

How to position the insertion cursor under the mouse pointer, after creating a tkinter text widget by a event?


I want the user to be able to edit a canvas-text item. As the canvas-text item has less functionality than the text-widget, I want to use the text-widget for editing. So when the editing is started, by mouse double click event, I open a new canvas-window item over the canvas-text item and put a text-widget in it. Then I insert the text of the canvas-text item into the text-widget. Of course the insertion cursor of the text-widget is now positioned at the end of the text-widget. But I want it to be positioned at the location, where the mouse double click happened. How can I do this?

This is my code:

import tkinter as tk

def edit_text(event):
    coords = canvas.bbox(canvas_text)
    text_ref = tk.Text(root, font=("Courier", 10))
    canvas_window = canvas.create_window(coords[0], coords[1], window=text_ref, anchor="nw")
    text_ref.bind("<Escape>", lambda event: store_edits(text_ref, canvas_window))
    text_ref.insert("1.0", canvas.itemcget(canvas_text, "text"))
    text_ref.focus_set()

def store_edits(text_ref, canvas_window):
    canvas.itemconfig(canvas_text, text=text_ref.get("1.0", "end"))
    canvas.delete(canvas_window)
    del text_ref

root = tk.Tk()
canvas = tk.Canvas(root)
canvas.grid()

canvas_text = canvas.create_text(100, 100, text="aaa\n456\n123\n123\n456\n123\nbbb\n", font=("Courier", 10))
canvas.tag_bind(canvas_text, "<Double-Button-1>", edit_text)

root.mainloop()

I ask, because I believe I am not the first one having this problem.


Solution

  • You can use an index of the form "@x,y" with a text widget, and you can compute the correct x,y based on the information passed in. The trick is that you need to wait until the text widget is visible to use the index, since tkinter can't compute the index until then.

    It might look something like this:

    def edit_text(event):
        coords = canvas.bbox(canvas_text)
        ...
        x = event.x - coords[0]
        y = event.y - coords[1]
        canvas.after_idle(lambda: text_ref.mark_set("insert", f"@{x},{y}"))
    

    That computation might be off slightly unless you account for the fact that the text widget has a border and highlightthickness that causes the text to be slightly offset from that on the canvas, but this is the general idea.