Click here to Skip to main content
Click here to Skip to main content
Go to top

Synchronized Scrolling of Multiple RichTextBoxes

, 8 Feb 2005
Rate this:
Please Sign up or sign in to vote.
Synchronize the scrolling of multiple RichTextBoxes using the EM_GETSCROLLPOS and EM_SETSCROLLPOS messages.

Introduction

I have noticed a lot of posts out there describing how to synchronize the scrolling of multiple RichTextBoxes. Most examples use the GetScrollPos() call and/or respond to VScroll/HScroll events. The problem I have experienced with this method is that the client area does not always match the scroll bar position and it is difficult to distinguish whether to use the trackPos or Pos value returned by the GetScrollInfo() when the scroll bar is being dragged. (This may be due to my ignorance on how to use these methods/APIs so please let me know…)

The RichTextBox does offer a way to determine and set the position of the client area as opposed to the position of the scroll bar using the EM_GETSCROLLPOS and EM_SETSCROLLPOS messages. Using this method allows you to accurately sync the client area instead of the scroll bar position which is really what we want.

Code usage

First declare the SendMessage API which will actually send the messages to the RTF control and declare the API constants. We will modify the lParam parameter in the SendMessage declaration to accept a POINT structure by reference:

Private Declare Auto Function SendScrollPosMessage _
                Lib "user32.dll" Alias "SendMessage" ( _
                ByVal hWnd As IntPtr, _
                ByVal Msg As Integer, _
                ByVal wParam As IntPtr, _
                ByRef lParam As POINT) As Integer

Private Const WM_USER = &H400
Private Const EM_GETSCROLLPOS = WM_USER + 221
Private Const EM_SETSCROLLPOS = WM_USER + 222

Private Structure POINT
        Public x As Integer
        Public y As Integer
End Structure

To retrieve the position of the client area you will need to send an EM_GETSCROLLPOS message to the RichTextbox. You will have to specify the handle to the target RichTextBox and pass the POINT structure which will contain the position after the message is sent:

Dim stcScrollPoint As New Point
SendMessage(rtfText.Handle(), EM_GETSCROLLPOS, _
                  New IntPtr(0), stcScrollPoint) 
Debug.WriteLine(stcScrollPoint.y)
Debug.WriteLine(stcScrollPoint.x)

To set the position of the client area you will need to send an EM_SETSCROLLPOS message to the RichTextBox. As mentioned above you need to specify the handle of the target RichTextBox and pass the POINT structure which will set the position after the message is sent:

Dim stcScrollPoint As New Point
stcScrollPoint.x = 20
stcScrollPoint.y = 40 
SendMessage(rtfText.Handle(), EM_SETSCROLLPOS, _
               New IntPtr(0), ptrScrollPoint)

Implementation

I have implemented this into a class that simply allows you to add RichTextBoxes and set the synchronization to include the horizontal, vertical or both scrollbars and it takes care of the rest. The usage of this class is as follows:

Private objScrollSync As New clsRTFScrollSync 
Public Sub FormLoad(...)... 
        objScrollSync.ScrollBarToSync = ScrollBars.Vertical 
        objScrollSync.AddControl(rtfML)
        objScrollSync.AddControl(rtfGutter) 
End Sub

The full source code:

Imports System.Runtime.InteropServices
Public Class clsRTFScrollSync
    Private Declare Auto Function SendScrollPosMessage _
                 Lib "user32.dll" Alias "SendMessage"( _
                 ByVal hWnd As IntPtr, _
                 ByVal Msg As Integer, _
                 ByVal wParam As IntPtr, _
                 ByRef lParam As POINT) As Integer
    Private Const WM_USER = &H400
    Private Const EM_GETSCROLLPOS = WM_USER + 221
    Private Const EM_SETSCROLLPOS = WM_USER + 222
    Private Structure POINT
        Public x As Integer
        Public y As Integer
    End Structure
    Private aControls As New ArrayList
    Private sbScrollBarType As Windows.Forms.ScrollBars
    Public Property ScrollBarToSync() As Windows.Forms.ScrollBars
        Get
            Return sbScrollBarType
        End Get
        Set(ByVal Value As Windows.Forms.ScrollBars)
            sbScrollBarType = Value
        End Set
    End Property
    Public Sub AddControl(ByVal RTFControl As Object)
        Dim objControlSubClass As New clsWindowSubClass(RTFControl.Handle, _
                                                            RTFControl, Me)
        aControls.Add(objControlSubClass)
    End Sub
    Public Sub SyncScrollBars(ByVal Handle As IntPtr, _
                              ByVal SubClass As clsWindowSubClass, _
                              ByVal Window As Object, _
                              ByRef WindowsMessage As Message)
        Static blnIgnoreMessages As Boolean
        If blnIgnoreMessages = True Then Exit Sub
        blnIgnoreMessages = True
        Dim blnChangeVertPos As Boolean = False
        Dim blnChangeHorizPos As Boolean = False
        Dim lngVertPos, lngHorizPos As Long
        Dim stcScrollPoint As New Point
        Dim ptrScrollPoint As IntPtr
        SendScrollPosMessage(Handle, EM_GETSCROLLPOS, _ 
                                    New IntPtr(0), stcScrollPoint)
        lngVertPos = stcScrollPoint.y
        lngHorizPos = stcScrollPoint.x
        If (sbScrollBarType = RichTextBoxScrollBars.Both Or _
            sbScrollBarType = RichTextBoxScrollBars.Vertical) Then 
                                               blnChangeVertPos = True
        If (sbScrollBarType = RichTextBoxScrollBars.Both Or _
            sbScrollBarType = RichTextBoxScrollBars.Horizontal) Then 
                                              blnChangeHorizPos = True
        If blnChangeVertPos = True Or blnChangeHorizPos = True Then
            Dim objSubClass As clsWindowSubClass
            Dim objWindowMessage As New Windows.Forms.Message
            For Each objSubClass In aControls
                If objSubClass.Handle.ToInt32 <> Handle.ToInt32 Then
                    SendScrollPosMessage(objSubClass.Handle, EM_GETSCROLLPOS, _ 
                                              New IntPtr(0), stcScrollPoint)
                    If blnChangeHorizPos = True Then 
                                   stcScrollPoint.x = lngHorizPos
                    If blnChangeVertPos = True Then 
                                   stcScrollPoint.y = lngVertPos
                    SendScrollPosMessage(objSubClass.Handle, EM_SETSCROLLPOS, _ 
                                               New IntPtr(0), stcScrollPoint)
                End If
            Next
        End If
        blnIgnoreMessages = False
    End Sub
End Class
Public Class clsWindowSubClass
    Inherits System.Windows.Forms.NativeWindow
    Private objWindow As Object
    Private objParent As clsRTFScrollSync
    Public Sub New(ByVal Handle As IntPtr, ByVal Window As Object, 
                                   ByVal Parent As clsRTFScrollSync)
        objWindow = Window
        objParent = Parent
        MyBase.AssignHandle(Handle)
    End Sub
    Protected Overrides Sub WndProc(ByRef WindowMessage As _
                                     system.Windows.Forms.Message)
        MyBase.WndProc(WindowMessage)
        objParent.SyncScrollBars(MyBase.Handle, Me, _
                                         objWindow, WindowMessage)
    End Sub
End Class

Credits

Special thanks to Georgi Atanasov for his input in this article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

mike-obrien

United States United States
No Biography provided

Comments and Discussions

 
GeneralWAY SIMPLER SOLUTION Without Unmanaged Code PinmemberVortran1-Feb-07 3:42 
Questionperformance problem with your code Pinmembertdmsoftwarex115-Aug-06 13:12 
Generalscroll a richtextbox without scrollbar Pinmemberscalpa9811-Jun-06 11:14 
QuestionAny way round the 16-bit limit? Pinmembergemani27-Feb-06 6:07 
AnswerRe: Any way round the 16-bit limit? PinmemberMattJ.Brunell9-Sep-07 12:05 
GeneralEditor with Line number PinmemberSanjeevimuhil12-Sep-05 22:56 
GeneralNo need to cast IntPtr to an int Pinsussagent-smith26-Jul-05 8:10 
GeneralRe: No need to cast IntPtr to an int Pinmembermike-obrien27-Jul-05 4:41 
GeneralThanks Pinmemberjohnnyboy1023-May-05 8:12 
GeneralHiding one of the scroll bars PinmemberDanDixonUS12-Jan-05 23:23 
GeneralRe: Hiding one of the scroll bars Pinmemberdata-boy24-Jan-05 14:10 
GeneralNo need of Marshal at all PinmemberGeorgi Atanasov4-Jan-05 22:51 
GeneralRe: No need of Marshal at all Pinmemberdata-boy5-Jan-05 6:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140922.1 | Last Updated 9 Feb 2005
Article Copyright 2005 by mike-obrien
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid