同步两个不同内容的控件的滚动位置

时间:2021-06-09 05:53:50

标签: vb.net winforms winapi scrollbar richtextbox

我用这个简单的代码同时设置了不同 RichTextBox 控件的两个 Scrollbars 的位置。
当 RichTextBox 的文本比另一个更长时,麻烦就来了。

有什么建议吗?如何计算差异的百分比,以同步两个控件的滚动位置,例如,在开始/中间/结束处,同时?

Const WM_USER As Integer = &H400
Const EM_GETSCROLLPOS As Integer = WM_USER + 221
Const EM_SETSCROLLPOS As Integer = WM_USER + 222
Declare Function SendMessage Lib "user32.dll" Alias "SendMessageW" (ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, ByRef lParam As Point) As Integer

Private Sub RichTextBox1_VScroll(sender As Object, e As EventArgs) Handles RichTextBox1.VScroll
    Dim pt As Point
    SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, pt)
    SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, pt)
End Sub

Private Sub RichTextBox2_VScroll(sender As Object, e As EventArgs) Handles RichTextBox2.VScroll
    Dim pt As Point
    SendMessage(RichTextBox2.Handle, EM_GETSCROLLPOS, 0, pt)
    SendMessage(RichTextBox1.Handle, EM_SETSCROLLPOS, 0, pt)
End Sub

1 个答案:

答案 0 :(得分:2)

此处描述了该过程:
How to scroll a RichTextBox control to a given point regardless of caret position

  • 您需要计算控件的最大滚动值

  • 考虑 ClientSize.HeightFont.Height:当我们定义最大滚动位置时,它们都起作用。最大垂直滚动值定义为:

    MaxVerticalScroll = Viewport.Height - ClientSize.Height + Font.Height - BorderSize  
    

    其中 Viewport 是包含其所有内容的控件的整体内部表面。
    它通常由 PreferredSize 属性(属于 Control 类)返回,但是,例如 RichTextBox,在文本换行之前设置 PreferredSize,因此它只是相对于未换行的文本,在这里用处不大。
    您可以手动确定基本距离(如上面的链接所述),或使用 GetScrollInfo() 函数。它返回一个 SCROLLINFO 结构,其中包含绝对的最小和最大滚动值以及当前滚动位置。

  • 计算两个最大滚动位置的相对差:这是用于缩放两个滚动位置的乘数因子,以生成一个共同的相对值。

重要:使用VScroll事件,必须引入一个变量,防止两个Control反复触发对方的Scroll动作,导致StackOverflow异常。
请参阅 VScroll 事件处理程序和 synchScroll 布尔字段的使用。

SyncScrollPosition() 方法调用计算相对值的 GetAbsoluteMaxVScroll()GetRelativeScrollDiff() 方法滚动值,然后调用 SendMessage 设置要同步的控件的滚动位置。
两者都接受 TextBoxBase 参数,因为 RichTextBox 派生自此基类,作为 TextBox 类,因此您可以对 RichTextBox 和 TextBox 控件使用相同的方法而无需任何更改。

▶ 使用您在此处找到的 SendMessage 声明等。

Private synchScroll As Boolean = False

Private Sub richTextBox1_VScroll(sender As Object, e As EventArgs) Handles RichTextBox1.VScroll
    If synchScroll Then Return
    synchScroll = True
    SyncScrollPosition(RichTextBox1, RichTextBox2)
    synchScroll = False
End Sub

Private Sub richTextBox2_VScroll(sender As Object, e As EventArgs) Handles RichTextBox2.VScroll
    If synchScroll Then Return
    synchScroll = True
    SyncScrollPosition(RichTextBox2, RichTextBox1)
    synchScroll = False
End Sub

Private Sub SyncScrollPosition(ctrlSource As TextBoxBase, ctrlDest As TextBoxBase)
    Dim infoSource = GetAbsoluteMaxVScroll(ctrlSource)
    Dim infoDest = GetAbsoluteMaxVScroll(ctrlDest)
    Dim relScrollDiff As Single = GetRelativeScrollDiff(infoSource.nMax, infoDest.nMax, ctrlSource, ctrlDest)

    Dim pt = New Point(0, CType((infoSource.nPos + 0.5F) * relScrollDiff, Integer))
    SendMessage(ctrlDest.Handle, EM_SETSCROLLPOS, 0, pt)
End Sub

Private Function GetAbsoluteMaxVScroll(ctrl As TextBoxBase) As SCROLLINFO
    Dim si = New SCROLLINFO(SBInfoMask.SIF_POSRANGE)
    GetScrollInfo(ctrl.Handle, SBParam.SB_VERT, si)
    Return si
End Function

Private Function GetRelativeScrollDiff(sourceScrollMax As Integer, destScrollMax As Integer, source As TextBoxBase, dest As TextBoxBase) As Single
    Dim border As Single = If(source.BorderStyle = BorderStyle.None, 0F, 1.0F)
    Return (CSng(destScrollMax) - dest.ClientSize.Height) / (sourceScrollMax - source.ClientSize.Height - border)
End Function

Win32 方法声明

Imports System.Runtime.InteropServices

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

<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As Integer, <[In], Out> ByRef lParam As Point) As Integer
End Function

<DllImport("user32.dll")>
Friend Shared Function GetScrollInfo(hwnd As IntPtr, fnBar As SBParam, ByRef lpsi As SCROLLINFO) As Boolean
End Function

<StructLayout(LayoutKind.Sequential)>
Friend Structure SCROLLINFO
    Public cbSize As UInteger
    Public fMask As SBInfoMask
    Public nMin As Integer
    Public nMax As Integer
    Public nPage As UInteger
    Public nPos As Integer
    Public nTrackPos As Integer

    Public Sub New(mask As SBInfoMask)
        cbSize = CType(Marshal.SizeOf(Of SCROLLINFO)(), UInteger)
        fMask = mask : nMin = 0 : nMax = 0 : nPage = 0 : nPos = 0 : nTrackPos = 0
    End Sub
End Structure

Friend Enum SBInfoMask As UInteger
    SIF_RANGE = &H1
    SIF_PAGE = &H2
    SIF_POS = &H4
    SIF_DISABLENOSCROLL = &H8
    SIF_TRACKPOS = &H10
    SIF_ALL = SIF_RANGE Or SIF_PAGE Or SIF_POS Or SIF_TRACKPOS
    SIF_POSRANGE = SIF_RANGE Or SIF_POS Or SIF_PAGE
End Enum

Friend Enum SBParam As Integer
    SB_HORZ = &H0
    SB_VERT = &H1
    SB_CTL = &H2
    SB_BOTH = &H3
End Enum

工作原理
请注意,两个控件使用不同的字体:

  • Segoe UI, 9.75pt 上面的控件
  • Microsoft Sans Serif, 9pt 另一个

ScrollBars Sychronize

相关问题