21

How can I sync the scrolling of two multiline textboxes in C# (WinForms)?

When you scroll up/down a line in TextBox A, TextBox B should scroll up/down too. The same the other way around.

Is this achievable without custom controls?

lesderid
  • 3,388
  • 8
  • 39
  • 65

5 Answers5

43

Yes, you'll have to create a custom text box so you can detect it scrolling. The trick is to pass the scroll message to the other text box so it will scroll in sync. This really only works well when that other text box is about the same size and has the same number of lines.

Add a new class to your project and paste the code shown below. Compile. Drop two of the new controls from the top of the toolbox onto your form. Set the Buddy property to the other control on both. Run, type some text in both of them and watch them scroll in sync as you drag the scrollbar.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class SyncTextBox : TextBox {
    public SyncTextBox() {
        this.Multiline = true;
        this.ScrollBars = ScrollBars.Vertical;
    }
    public Control Buddy { get; set; }

    private static bool scrolling;   // In case buddy tries to scroll us
    protected override void WndProc(ref Message m) {
        base.WndProc(ref m);
        // Trap WM_VSCROLL message and pass to buddy
        if (m.Msg == 0x115 && !scrolling && Buddy != null && Buddy.IsHandleCreated) {
            scrolling = true;
            SendMessage(Buddy.Handle, m.Msg, m.WParam, m.LParam);
            scrolling = false;
        }
    }
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • @Hans Passant : i wanted to achieve similar but in case of two list views. i tried using this code but its not working. do i need to add something in this ? – Iorn Man Feb 06 '13 at 10:56
  • 1
    @Hans Passant : Gr8 man, actully i had set buddy property at wrong place. gr8 job. – Iorn Man Feb 06 '13 at 12:06
  • Any thoughts on editing the answer to include the mouse wheel, and scrolling with the cursor keys (+ home/page up/down etc.) ? – Dan W Feb 27 '15 at 01:46
  • 1
    That already works, only mistake you can make is not trying it. – Hans Passant Feb 27 '15 at 07:21
  • How about when you drag the scroll slide and mouse left button pressed you move the slide up and down, them the buddy list view doesn't react to the scrolling? – Tiger Galo Jan 26 '18 at 13:50
11

You can change this line:

if (m.Msg == 0x115) && !scrolling && Buddy != null && Buddy.IsHandleCreated)

to this:

if ((m.Msg == 0x115 || m.Msg==0x20a) && !scrolling && Buddy != null && Buddy.IsHandleCreated)

and it will support scrolling with the mouse wheel as well.

John Willemse
  • 6,608
  • 7
  • 31
  • 45
  • Unfortunately, although this appears to work, the scrolling gets out of sync with the mouse wheel, and the main textbox scrolls faster than the buddy. – Keith Davidson Aug 22 '16 at 01:33
  • How about when you drag the scroll slide and mouse left button pressed you move the slide up and down, them the buddy list view doesn't react to the scrolling? – Tiger Galo Jan 24 '18 at 19:14
3

Hans Passant's solution worked like a charm but I needed a RichTextBox with both horizontal and vertical scrollbars. If you extend a RichTextBox instead of a TextBox you'll need to change the ScrollBars property accordingly (I used RichTextBoxScrollBars.Both).

If you want to sync horizontal scrolling as well, look for (m.Msg == 0x115) || (m.Msg == 0x114).

josliber
  • 43,891
  • 12
  • 98
  • 133
3

Hans Passant's solution was awesome. However I needed to sync three text boxes not just two.

So I modified it a little - but all credence should go to Hans, there's no way I would have even got close without his work - I thought I would post it back here in case others need the same.

SyncBox class:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class SyncTextBox : TextBox
{
    public SyncTextBox()
    {
        this.Multiline = true;
        this.ScrollBars = ScrollBars.Vertical;
    }

    public Control[] Buddies { get; set; }

    private static bool scrolling;   // In case buddy tries to scroll us
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        // Trap WM_VSCROLL message and pass to buddy
        if (Buddies != null)
        {
            foreach (Control ctr in Buddies)
            {
                if (ctr != this)
                {
                    if ((m.Msg == 0x115 || m.Msg == 0x20a) && !scrolling && ctr.IsHandleCreated)
                    {
                        scrolling = true;
                        SendMessage(ctr.Handle, m.Msg, m.WParam, m.LParam);
                        scrolling = false;
                    }
                }
            }
        }
    }
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}

Then in the form initilizer:

// add the required controls into scroll sync
Control[] syncedCtrls = new Control[] { ctrl1, ctrl2, ..., ctrln };
foreach (SyncTextBox ctr in syncedCtrls)
{
    ctr.Buddies = syncedCtrls;
}
Morvael
  • 3,478
  • 3
  • 36
  • 53
2

Here is what finally helped me to fix synchronization of multiple textboxes using mouse wheel.

I based it on very helpful Hans example.

int WM_MOUSEWHEEL   = 0x20a; // or 522
int WM_VSCROLL      = 0x115; // or 277

protected override void WndProc(ref Message m)
{
        //Trap WM_VSCROLL and WM_MOUSEWHEEL message and pass to buddy
        if (Buddies != null)
        {

            if (m.Msg == WM_MOUSEWHEEL)  //mouse wheel 
            {

                if ((int)m.WParam < 0)  //mouse wheel scrolls down
                    SendMessage(this.Handle, (int)0x0115, new IntPtr(1), new IntPtr(0)); //WParam: 1- scroll down, 0- scroll up
                else if ((int)m.WParam > 0)
                    SendMessage(this.Handle, (int)0x0115, new IntPtr(0), new IntPtr(0));



                return; //prevent base.WndProc() from messing synchronization up 
            }
            else if (m.Msg == WM_VSCROLL)
            {
                foreach (Control ctr in Buddies)
                {
                    if (ctr != this && !scrolling && ctr != null && ctr.IsHandleCreated)
                    {
                        scrolling = true;
                        SendMessage(ctr.Handle, m.Msg, m.WParam, m.LParam);
                        scrolling = false;
                    }

                }

            }
        }

    //do the usual
    base.WndProc(ref m);
}
Michael Ros
  • 1,171
  • 9
  • 9