Synchronize Scroll Position of two RichTextBoxes?

20,801

Solution 1

I did this for a small project a while ago, and here's the simplist solution I found.

Create a new control by subclassing RichTextBox:

   public class SynchronizedScrollRichTextBox : System.Windows.Forms.RichTextBox
    {
        public event vScrollEventHandler vScroll;
        public delegate void vScrollEventHandler(System.Windows.Forms.Message message);

        public const int WM_VSCROLL = 0x115;

        protected override void WndProc(ref System.Windows.Forms.Message msg) {
            if (msg.Msg == WM_VSCROLL) {
                if (vScroll != null) {
                    vScroll(msg);
                }
            }
            base.WndProc(ref msg);
        }

        public void PubWndProc(ref System.Windows.Forms.Message msg) {
            base.WndProc(ref msg);
        }
    }     

Add the new control to your form and for each control explicitly notify the other instances of the control that its vScroll position has changed. Somthing like this:

private void scrollSyncTxtBox1_vScroll(Message msg) {
    msg.HWnd = scrollSyncTxtBox2.Handle;
    scrollSyncTxtBox2.PubWndProc(ref msg);
}

I think this code has problems if all the 'linked' controls don't have the same number of displayable lines.

Solution 2

Thanks Jay for your answer; after some more searching I also found the method described here. I'll outline it below for anyone else interested.


First, declare the following enums:

public enum ScrollBarType : uint {
   SbHorz = 0,
   SbVert = 1,
   SbCtl = 2,
   SbBoth = 3
 }

public enum Message : uint {
   WM_VSCROLL = 0x0115
}

public enum ScrollBarCommands : uint {
   SB_THUMBPOSITION = 4
}

Next, add external references to GetScrollPos and SendMessage.

[DllImport( "User32.dll" )]
public extern static int GetScrollPos( IntPtr hWnd, int nBar );

[DllImport( "User32.dll" )]
public extern static int SendMessage( IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam );

Finally, add an event handler for the VScroll event of the appropriate RichTextBox:

private void myRichTextBox1_VScroll( object sender, EventArgs e )
{
   int nPos = GetScrollPos( richTextBox1.Handle, (int)ScrollBarType.SbVert );
   nPos <<= 16;
   uint wParam = (uint)ScrollBarCommands.SB_THUMBPOSITION | (uint)nPos;
   SendMessage( richTextBox2.Handle, (int)Message.WM_VSCROLL, new IntPtr( wParam ), new IntPtr( 0 ) );
}

In this case, richTextBox2's vertical scroll position will be synchronized with richTextBox1.

Solution 3

[Visual Studio C# 2010 Express, v10.0.30319 on a Windows 7 64bit installation]

I've used Donut's solution posted above, but found a problem when scrolling to the end of RichTextBoxes that contain many lines.

If the result of GetScrollPos() is >0x7FFF then when nPos is shifted, the top bit is set. The creation of the IntPtr with the resulting wParam variable will then fail with an OverflowException. You can easily test this with the following (the second line will fail):

    IntPtr ip = new IntPtr(0x7FFF0000);
    IntPtr ip2 = new IntPtr(0x80000000);

A version of SendMessage() that uses UIntPtr would appear to be a solution, but I couldn't get that to work. So, I've use the following:

    [DllImport("User32.dll")]
    public extern static int SendMessage(IntPtr hWnd, uint msg, UInt32 wParam, UInt32 lParam);

This should be good up to 0xffff, but would fail after that. I've not yet experienced a >0xffff result from GetScrollPos(), and assume that User32.dll is unlikely to have a 64bit version of SendCommand(), but any solutions to that problem would be greatly appreciated.

Solution 4

const int WM_USER = 0x400;

const int EM_GETSCROLLPOS = WM_USER + 221;

const int EM_SETSCROLLPOS = WM_USER + 222;

[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref Point lParam);

private void RichTextBox1_VScroll(object sender, EventArgs e)
{
    Point pt;

    SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);

    SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}


private void RichTextBox2_VScroll(object sender, EventArgs e)
{
    Point pt;

    SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);

    SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}

Solution 5

A variation of Jay's subclass approach can be found in Joseph Kingry's answer here: Synchronizing Multiline Textbox Positions in C#.

Joseph's approach also subclasses but doesn't require a _VScroll event handler. I used that approach to do a 3-way bind between 3 boxes and added WM_HSCROLL.

Share:
20,801
Donut
Author by

Donut

Mmm... donuts.

Updated on July 11, 2021

Comments

  • Donut
    Donut almost 3 years

    In my application's form, I have two RichTextBox objects. They will both always have the same number of lines of text. I would like to "synchronize" the vertical scrolling between these two, so that when the user changes the vertical scroll position on one, the other scrolls the same amount. How might I go about doing this?

  • Tun
    Tun almost 13 years
    This method working fine for scrolling by moving the scrollbar. But the scroll bars are not synchronized when (1) scrolling with mouse wheel (2) scrolling by page down or page up keys - scrolling by pressing the arrow keys
  • Pomster
    Pomster almost 12 years
    @Jay riggs 'System.Windows.Forms.RichTextBox' does not contain a definition for 'PubWndProc' and no extension method 'PubWndProc' accepting a first argument of type 'System.Windows.Forms.RichTextBox' could be found (are you missing a using directive or an assembly reference?)
  • Aditya Bokade
    Aditya Bokade over 11 years
    Great summary. Saved my lot of time. Thank you!