C#: Synchronize Scroll Position of two RichTextBoxes?

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.


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.


[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.