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 "synchro开发者_JAVA百科nize" 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?
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.
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);
}
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.
@Sudhakar MuthuKrishnan's answer needs some fixes, but works. Thanks!
First GetScrollPos which rised event and then set scroll position for others.
private void RichTextBox1_VScroll(object sender, EventArgs e)
{
Point pt = new Point();
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 = new Point();
SendMessage(RichTextBox2.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox1.Handle, EM_SETSCROLLPOS, 0, ref pt);
}
精彩评论