Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Synchronized Scrolling of Multiple RichTextBoxes

0.00/5 (No votes)
8 Feb 2005 14  
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