The question is simple:

I have two scrolled text widgets (I'm using tkinter and python 3.x), and I want to synchronize them, that is I want both scrollbars to move at the same time when the user moves the mouse wheel.

Both widgets are suppossed to contain the same text, and the user is going to compare the information in both, so it is necessary that he can scroll both boxes at once.


Thanks.

Dani AI

Generated

The original question from is a common one; ’s pointer to a class-based recipe is a valid direction, but a small function-based wiring often works more simply for two Text widgets that must stay aligned. The recipe approach can be useful if a reusable widget is wanted; the alternative below shows a minimal, cross-platform pattern that keeps two independent scrollbars and makes mouse-wheel, scrollbar-drag and programmatic scrolling act in lockstep.

import tkinter as tk
import sys

root = tk.Tk()

def make_synced_texts(parent):
    left = tk.Frame(parent); right = tk.Frame(parent)
    left.pack(side='left', fill='both', expand=True)
    right.pack(side='left', fill='both', expand=True)

    text1 = tk.Text(left, wrap='none'); sb1 = tk.Scrollbar(left, orient='vertical')
    text1.pack(side='left', fill='both', expand=True); sb1.pack(side='right', fill='y')
    text1.config(yscrollcommand=sb1.set)

    text2 = tk.Text(right, wrap='none'); sb2 = tk.Scrollbar(right, orient='vertical')
    text2.pack(side='left', fill='both', expand=True); sb2.pack(side='right', fill='y')
    text2.config(yscrollcommand=sb2.set)

    def sync_scroll(*args):
        # args are ("moveto", frac) or ("scroll", n, "units"/"pages")
        text1.yview(*args); text2.yview(*args)

    sb1.config(command=sync_scroll); sb2.config(command=sync_scroll)

    def on_mousewheel(event):
        num = getattr(event, 'num', None); delta = getattr(event, 'delta', 0)
        if num == 4: step = -1
        elif num == 5: step = 1
        elif sys.platform == 'darwin': step = -int(delta)
        else:
            step = int(-delta / 120)
            if step == 0: step = -1 if delta > 0 else 1
        text1.yview_scroll(step, 'units'); text2.yview_scroll(step, 'units')
        return 'break'

    for w in (text1, text2):
        if sys.platform.startswith('linux'):
            w.bind('<Button-4>', on_mousewheel); w.bind('<Button-5>', on_mousewheel)
        else:
            w.bind('<MouseWheel>', on_mousewheel)

    return text1, text2

t1, t2 = make_synced_texts(root)
t1.insert('1.0', 'Line\\n' * 300); t2.insert('1.0', t1.get('1.0','end'))
root.mainloop()

Notes and troubleshooting: mouse-wheel events are platform-dependent (Windows, X11 Button-4/5, macOS), so the handler above normalizes them. For visually exact line alignment both texts must use identical fonts, widths and wrap settings — otherwise fractional scrolling won’t line up if wrapping or fonts differ. If alignment must be exact by text index, synchronize by the first visible index (e.g. read text1.index('@0,0') and call text2.see(index)) rather than by fractional yview. Returning 'break' in the wheel handler prevents default double-scrolling on some platforms.

Recommended Answers

All 3 Replies

Well, I had the same exact problem a few months ago. I used the code found , and found it is pretty good, but not perfect. For example, if you click within one of the listboxes and scroll with the mouse wheel, they do not scroll synchronously. But that's the best I could find. Good luck.

hmmm, how do you use these class guys?

I still do not know object oriente programming, my program is event-oriented.

Can I copy that code and leave it inside a separate file, then import it into my program?

If so, how do I make a widget in my program using that class?

Yes, you should probably just copy that code into a separate file and then import it into your program. From there, you can create the widget in your main script similarly to a normal widget. This example would be some sort of a music player:

import MultiListbox

frame = Frame(root, relief=SUNKEN)
mlb = MultiListbox.MultiListbox(frame, (('Title', 15), ('Artist', 15), ('Length', 12)))
for i in range(1000):
    mlb.insert(END, ('Test Song %d' % i, 'Test Artist %d' % i, 'Length %d' % i))
mlb.pack(expand=YES,fill=BOTH)

Post back if you have any other questions.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.