Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Synchronize Scroll Position of two RichTextBoxes?

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?

like image 802
Donut Avatar asked Dec 01 '09 16:12

Donut


5 Answers

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.

like image 122
Donut Avatar answered Nov 17 '22 00:11

Donut


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.

like image 29
Jay Riggs Avatar answered Nov 17 '22 01:11

Jay Riggs


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

like image 41
Gordon Avatar answered Nov 17 '22 02:11

Gordon


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.

like image 1
Jim Fred Avatar answered Nov 17 '22 01:11

Jim Fred


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);
}
like image 2
Sudhakar MuthuKrishnan Avatar answered Nov 17 '22 02:11

Sudhakar MuthuKrishnan