С#: Синхронизировать положение прокрутки двух RichTextBoxes?

В моей форме приложения у меня есть два объекта RichTextBox. Они оба будут иметь одинаковое количество строк текста. Я хотел бы "синхронизировать" вертикальную прокрутку между этими двумя, так что, когда пользователь меняет положение вертикальной прокрутки на один, другой прокручивает ту же сумму. Как я могу это сделать?

Ответы

Ответ 1

Я сделал это для небольшого проекта некоторое время назад, и вот упрощенное решение, которое я нашел.

Создайте новый элемент управления путем подкласса 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);
        }
    }     

Добавьте новый элемент управления в свою форму и для каждого элемента управления явным образом уведомите другие экземпляры элемента управления о том, что его позиция vScroll была изменена. Что-то вроде этого:

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

Я думаю, что этот код имеет проблемы, если все "связанные" элементы управления не имеют одинакового количества отображаемых строк.

Ответ 2

Спасибо Джей за ваш ответ; после некоторого поиска я также нашел способ, описанный здесь. Я расскажу об этом ниже для всех, кого это интересует.


Сначала объявите следующие перечисления:

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
}

Затем добавьте внешние ссылки на GetScrollPos и 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 );

Наконец, добавьте обработчик событий для события VScroll соответствующего 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 ) );
}

В этом случае положение вертикальной прокрутки richTextBox2 будет синхронизироваться с richTextBox1.

Ответ 3

[Visual Studio С# 2010 Express, v10.0.30319 на установке Windows 7 с 64-разрядной версией]

Я использовал решение Donut выше, но обнаружил проблему при прокрутке до конца RichTextBoxes, которые содержат много строк.

Если результат GetScrollPos() равен >0x7FFF, тогда, когда nPos сдвинут, верхний бит установлен. При создании IntPtr с результирующей переменной wParam произойдет сбой OverflowException. Вы можете легко проверить это следующим образом (вторая строка не будет выполнена):

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

Версия SendMessage(), которая использует UIntPtr, будет решением, но я не мог заставить это работать. Итак, я использую следующее:

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

Это должно быть хорошо до 0xffff, но после этого произойдет сбой. Я еще не испытал результат >0xffff от GetScrollPos() и предположил, что User32.dll вряд ли будет иметь 64-битную версию SendCommand(), но любые решения этой проблемы будут с благодарностью.

Ответ 4

Вариант подкласса Jay можно найти в сообщении Джозефа Кингри здесь: Синхронизация многострочных позиций текстового поля в С#. Джозеф также подклассы, но не требует обработчика события _VScroll. Я использовал этот подход, чтобы выполнить трехстороннее связывание между 3-мя ящиками и добавленным WM_HSCROLL.