Search code examples
pythontkintertkinter.text

find index of elements created with window_create inside tkinter Text


I used window_create to create interactive buttons inside a Text element. The buttons represent random or static values and I want to be able to compile the contents of the text element and replace the buttons with their respective values. However, I cannot find where any of the buttons are.

I've tried self.text.get("1.0",tk.END), but it only returns the text, not including the button elements

the button elements are created like so:

btn_text = tk.StringVar()
value = StaticValue('static', btn_text, self.custom_val_veiwer, idx)
button = tk.Button(self.text, 
                        textvariable=btn_text, command=lambda v=value: 
                        self.veiw_custom_val(None, val=v))
btn_text.set('static')
self.custom_vals.append(value)
self.text.window_create(tk.INSERT, window=button)

edit: if you want to recreate the problem use this:

import tkinter as tk

root = tk.Tk()
text = tk.Text(root)
text.pack()

text.insert(tk.END, 'before button')

button = tk.Button(text, text='button')
text.window_create(tk.END, window=button)

text.insert(tk.END, 'after button')
print(text.get("1.0",tk.END))
root.mainloop()

notice how the button appears in the text field, but it is not printed out

(the output is before buttonafter button I want someting like before button<button>after button or a function that would tell me there is a button at row x at index y)


Solution

  • There's nothing that gives you exactly what you want, but it just takes a couple lines of code to get the index of the clicked button.

    What I would do is have the button pass a reference to itself as an argument to the command. That requires making the button in two steps, since you can't reference the button before it is created.

    button = tk.Button(text, text="button")
    button.configure(command=lambda button=button: handle_click(button))
    

    In the function called by the button, you can use the text widget dump command to get a list of all windows, and from that you can find the index of the button. The dump command will return a list of tuples. Each tuple has a key (in this case, "window"), the window name, and the index of the window. You can iterate over the result of that command to find the index of the button which was passed to the function.

    def handle_click(button):
        for (key, name, index) in text.dump("1.0", "end", window=True):
            if name == str(button):
                print("you clicked on the button at index {}".format(index))
                break
    

    Example

    Here is a contrived example that adds several buttons. Clicking on the button will display the index of that button in a label. Notice how it will continue to work even if you manually edit the text widget to change the index of the button.

    import tkinter as tk
    
    root = tk.Tk()
    text = tk.Text(root)
    label = tk.Label(root)
    label.pack(side="top", fill="x")
    text.pack(side="top", fill="both", expand=True)
    
    def handle_click(button):
        for (key, name, index) in text.dump("1.0", "end", window=True):
            if name == str(button):
                label.configure(text="You clicked on the button at {}".format(index))
                break
    
    for word in ("one", "two", "three", "four", "five"):
        text.insert("end", word + "\n")
        button = tk.Button(text, text="click me")
        button.configure(command=lambda button=button: handle_click(button))
        text.window_create("insert-1c", window=button)
    
    tk.mainloop()