pythonlistboxcursorvisibletext-widget

Showing cursor in Text widget after selecting Listbox option


The following Python code generates a list box and text widget. When I select an option, the cursor should show in the text widget but it does not. However, if I press Tab, the text widget is activated and then I can see the cursor.

import tkinter as tk

list_cursor_pos = ['2.5', '5.10', '8.15']

def on_listbox_select(event):
    i = listbox.curselection()[0]
    cursor_pos = list_cursor_pos[i]

    text_box.focus_set()
    text_box.mark_set("insert", cursor_pos)
    text_box.see(cursor_pos)
    text_box.event_generate('<FocusIn>')
    
root = tk.Tk()

# Listbox
listbox = tk.Listbox(root)
listbox.pack(side = tk.LEFT, padx=10, pady=10)
for e in list_cursor_pos:
    listbox.insert('end', f'Go to {e} in the text')
listbox.bind("<<ListboxSelect>>", on_listbox_select)

# Text box
text_box = tk.Text(root, height=15, width=50)
text_box.pack(side = tk.RIGHT, padx=10, pady=10)
# Create content for text box
content = ""
for i in range(9):
    content += f"{i+1}:234567890123456789\n"
text_box.insert("1.0", content)

root.mainloop()

When I do the same using a button instead of a list box, it works.

import tkinter as tk

def set_cursor_pos():
    cursor_pos = "2.5"
    text_box.focus_set()
    text_box.mark_set("insert", cursor_pos)

root = tk.Tk()

myButton = tk.Button(root, text = "Set cursor position at 2.5", command=set_cursor_pos)
myButton.pack(side = tk.LEFT, padx=10, pady=10)
myButton.focus_set()

# Text box
text_box = tk.Text(root, height=15, width=50)
text_box.pack(side = tk.RIGHT, padx=10, pady=10)
content = ""
for i in range(9):
    content += f"{i+1}:234567890123456789\n"
text_box.insert("1.0", content)

root.mainloop()

Solution

  • << >> means VirtualEvent, I'm not sure but it may work in different way than normal events in command= and in < >. Maybe it runs your code before it sets focus on Listbox, so your code may set focus on Text but later tkinter sets it on Listbox.

    I found two methods for this:

    1. use after() to execute focus_set() little later:
    def on_listbox_select(event):
        i = listbox.curselection()[0]
        cursor_pos = list_cursor_pos[i]
    
        text_box.mark_set("insert", cursor_pos)
        text_box.see(cursor_pos)
        #text_box.event_generate('<FocusIn>')
    
        root.after(100, change_focus)
    
    def change_focus():
        text_box.focus_set()
    
    1. use event <FocusIn> in Listbox to execute it
    def on_listbox_select(event):
        i = listbox.curselection()[0]
        cursor_pos = list_cursor_pos[i]
    
        text_box.mark_set("insert", cursor_pos)
        text_box.see(cursor_pos)
        #text_box.event_generate('<FocusIn>')
    
    def change_focus(event):
        text_box.focus_set()
    
    listbox.bind("<FocusIn>", change_focus)
    

    Full working code used for tests:

    I don't know why but if I select element on list and next I use Shift+Arrow (left, right, up or down) (when cursor is already in text) then it executes on_listbox_select again but listbox.curselection() gives empty list and it raises error on [0] - so I had to add if not i: return to solve it.

    import tkinter as tk
    
    list_cursor_pos = ['2.5', '5.10', '8.15']
    
    def on_listbox_select(event):
        print(event)
    
        i = listbox.curselection()
        if not i:   # resolve problem with `Shift-Arrow`
            return
    
        i = i[0]
        cursor_pos = list_cursor_pos[i]
    
        text_box.mark_set("insert", cursor_pos)
        text_box.see(cursor_pos)
        #text_box.event_generate('<FocusIn>')
    
        #root.after(100, change_focus)
    
    def change_focus(event=None):  # I use `event=None` to work with `after()` without changes in this function
        text_box.focus_set()
    
    root = tk.Tk()
    
    # Listbox
    listbox = tk.Listbox(root, takefocus=0)
    listbox.pack(side='left', padx=10, pady=10)
    
    for e in list_cursor_pos:
        listbox.insert('end', f'Go to {e} in the text')
    listbox.bind("<<ListboxSelect>>", on_listbox_select)
    listbox.bind("<FocusIn>", change_focus)
    
    # Text box
    text_box = tk.Text(root, height=15, width=50)
    text_box.pack(side='right', padx=10, pady=10)
    
    # Create content for text box
    content = ""
    for i in range(1, 10):
        content += f"{i}:234567890123456789\n"
    text_box.insert("1.0", content)
    
    root.mainloop()