Search code examples
pythontkintertextscrollbar

Scroll two text widgets simultaneously with mousewheel


I have one text widget where input can be entered by the user and I'm using a second text widget to display corresponding line numbers. I managed to scroll both simultaneously with one scrollbar using this solution https://stackoverflow.com/a/58190229/. But this only works when I either drag the scrollbar with the mouse or using the mousewheel while hovering on the scrollbar.

But when the mouse is hovering over the entered text and the the mousewheel is used only this text widget is scrolled while the other doesn't move. I tried to bind the mousewheel to the same function scrolling both text widgets but then I only receive the following error when hovering over the text and using the mousewheel: _tkinter.TclError: bad text index "<MouseWheel event send_event=True state=Mod1 delta=120 x=324 y=163>"

Here is the code:

import tkinter as tk

def multiple_yview(*args):
    text.yview(*args)
    line_numbers.yview(*args)

root = tk.Tk()
text_frame = tk.Frame(root)
scrollbar = tk.Scrollbar(text_frame)
line_numbers = tk.Text(text_frame, width=9, state="disabled", bd=0, bg="#F0F0F0")
text = tk.Text(text_frame, yscrollcommand=scrollbar.set, wrap="none")
text.bind('<MouseWheel>', multiple_yview)
scrollbar.config(command=multiple_yview)

for i in range(0, 100):  # For entering some text and line numbers
    text.insert("end", "A" + str(i) + "\n")
    line_numbers.configure(state="normal")
    line_numbers.insert("end", str(i) + ") \n")
    line_numbers.configure(state="disabled")

text_frame.grid(row=0, column=0)
line_numbers.grid(row=0, column=0)
text.grid(row=0, column=1)
scrollbar.grid(row=0, column=2, sticky= tk.N + tk.S)

root.mainloop()

Solution

  • If you print out args, you will find that the content of args is different for both command option and event binding.

    You need to convert args of event binding to the format used by yview and return "break" to disable the default handler of the event:

    def multiple_yview(*args):
        rv = None
        if isinstance(args[0], tk.Event):
            args = ('scroll', args[0].delta//-30, "units")
            rv = "break"  # disable default handler
        text.yview(*args)
        line_numbers.yview(*args)
        return rv
    

    Note that you need to bind <MouseWheel> event on line_numbers as well.