同步两个内容不同的控件的滚动位置
Synchronize the Scroll position of two Controls with different content
我使用这个简单的代码同时设置不同 RichTextBox 控件的两个滚动条的位置。
当 RichTextBox 的文本比另一个长时,问题就来了。
有什么建议吗?如何计算差异的百分比,以同步两个控件的滚动位置,例如,同时在 start/middle/end?
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
程序描述如下:
您需要计算控件的最大滚动值
考虑 ClientSize.Height
和 Font.Height
:当我们定义最大滚动位置时,两者都起作用。最大垂直滚动值定义为:
MaxVerticalScroll = Viewport.Height - ClientSize.Height + Font.Height - BorderSize
其中 Viewport 是包含其所有内容的控件的整体内表面。
它通常由 PreferredSize 属性(属于 Control
class)返回,但是,例如 RichTextBox,在文本环绕之前设置 PreferredSize
,所以它只是相对于展开的文本,在这里并不是很有用。
您可以手动确定基本距离(如上文link中所述),或使用包含绝对最小和最大滚动值以及当前滚动值的GetScrollInfo() function. It returns a SCROLLINFO结构滚动位置。
计算两个最大滚动位置的相对差异:这是用于缩放两个滚动位置的乘数,以生成一个共同的相对值。
重要:使用VScroll
事件,必须引入一个变量,防止两个Control反复触发对方的Scroll动作,导致Whosebug异常
请参阅 VScroll 事件处理程序和 synchScroll
布尔字段的使用。
▶ SyncScrollPosition()
方法调用 GetAbsoluteMaxVScroll()
和 GetRelativeScrollDiff()
计算相对滚动值的方法,然后调用 SendMessage 设置要同步的控件的滚动位置。
两者都接受 TextBoxBase
个参数,因为 RichTextBox 派生自这个基础 class,作为 TextBox class,因此您可以对 RichTextBox 和 TextBox 控件使用相同的方法而无需任何更改。
▶ 使用您在此处找到的 SendMessage
声明等。
Private synchScroll As Boolean = False
Private Sub richTextBox1_VScroll(sender As Object, e As EventArgs) Handles RichTextBox1.VScroll
SyncScrollPosition(RichTextBox1, RichTextBox2)
End Sub
Private Sub richTextBox2_VScroll(sender As Object, e As EventArgs) Handles RichTextBox2.VScroll
SyncScrollPosition(RichTextBox2, RichTextBox1)
End Sub
Private Sub SyncScrollPosition(ctrlSource As TextBoxBase, ctrlDest As TextBoxBase)
If synchScroll Then Return
synchScroll = True
Dim infoSource = GetAbsoluteMaxVScroll(ctrlSource)
Dim infoDest = GetAbsoluteMaxVScroll(ctrlDest)
Dim relScrollDiff As Single = GetRelativeScrollDiff(infoSource.nMax, infoDest.nMax, ctrlSource, ctrlDest)
Dim nPos = If(infoSource.nTrackPos > 0, infoSource.nTrackPos, infoSource.nPos)
Dim pt = New Point(0, CType((nPos + 0.5F) * relScrollDiff, Integer))
SendMessage(ctrlDest.Handle, EM_SETSCROLLPOS, 0, pt)
synchScroll = False
End Sub
Private Function GetAbsoluteMaxVScroll(ctrl As TextBoxBase) As SCROLLINFO
Dim si = New SCROLLINFO(SBInfoMask.SIF_ALL)
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
另一个
C#版本:
private bool synchScroll = false;
private void richTextBox1_VScroll(object sender, EventArgs e)
{
SyncScrollPosition(richTextBox1, richTextBox2);
}
private void richTextBox2_VScroll(object sender, EventArgs e)
{
SyncScrollPosition(richTextBox2, richTextBox1);
}
private void SyncScrollPosition(TextBoxBase ctrlSource, TextBoxBase ctrlDest) {
if (synchScroll) return;
synchScroll = true;
var infoSource = GetAbsoluteMaxVScroll(ctrlSource);
var infoDest = GetAbsoluteMaxVScroll(ctrlDest);
float relScrollDiff = GetRelativeScrollDiff(infoSource.nMax, infoDest.nMax, ctrlSource, ctrlDest);
int nPos = infoSource.nTrackPos > 0 ? infoSource.nTrackPos : infoSource.nPos;
var pt = new Point(0, (int)((nPos + 0.5F) * relScrollDiff));
SendMessage(ctrlDest.Handle, EM_SETSCROLLPOS, 0, ref pt);
synchScroll = false;
}
private SCROLLINFO GetAbsoluteMaxVScroll(TextBoxBase ctrl) {
var si = new SCROLLINFO(SBInfoMask.SIF_ALL);
GetScrollInfo(ctrl.Handle, SBParam.SB_VERT, ref si);
return si;
}
private float GetRelativeScrollDiff(int sourceScrollMax, int destScrollMax, TextBoxBase source, TextBoxBase dest) {
float border = source.BorderStyle == BorderStyle.None ? 0F : 1.0F;
return ((float)destScrollMax - dest.ClientSize.Height) / ((float)sourceScrollMax - source.ClientSize.Height - border);
}
声明:
using System.Runtime.InteropServices;
private const int WM_USER = 0x400;
private const int EM_GETSCROLLPOS = WM_USER + 221;
private const int EM_SETSCROLLPOS = WM_USER + 222;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out] ref Point lParam);
[DllImport("user32.dll")]
internal static extern bool GetScrollInfo(IntPtr hwnd, SBParam fnBar, ref SCROLLINFO lpsi);
[StructLayout(LayoutKind.Sequential)]
internal struct SCROLLINFO {
public uint cbSize;
public SBInfoMask fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
public SCROLLINFO(SBInfoMask mask)
{
cbSize = (uint)Marshal.SizeOf<SCROLLINFO>();
fMask = mask; nMin = 0; nMax = 0; nPage = 0; nPos = 0; nTrackPos = 0;
}
}
internal enum SBInfoMask : uint {
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS,
SIF_POSRANGE = SIF_RANGE | SIF_POS | SIF_PAGE
}
internal enum SBParam : int {
SB_HORZ = 0x0,
SB_VERT = 0x1,
SB_CTL = 0x2,
SB_BOTH = 0x3
}
我使用这个简单的代码同时设置不同 RichTextBox 控件的两个滚动条的位置。
当 RichTextBox 的文本比另一个长时,问题就来了。
有什么建议吗?如何计算差异的百分比,以同步两个控件的滚动位置,例如,同时在 start/middle/end?
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
程序描述如下:
您需要计算控件的最大滚动值
考虑
ClientSize.Height
和Font.Height
:当我们定义最大滚动位置时,两者都起作用。最大垂直滚动值定义为:MaxVerticalScroll = Viewport.Height - ClientSize.Height + Font.Height - BorderSize
其中 Viewport 是包含其所有内容的控件的整体内表面。
它通常由 PreferredSize 属性(属于Control
class)返回,但是,例如 RichTextBox,在文本环绕之前设置PreferredSize
,所以它只是相对于展开的文本,在这里并不是很有用。
您可以手动确定基本距离(如上文link中所述),或使用包含绝对最小和最大滚动值以及当前滚动值的GetScrollInfo() function. It returns a SCROLLINFO结构滚动位置。计算两个最大滚动位置的相对差异:这是用于缩放两个滚动位置的乘数,以生成一个共同的相对值。
重要:使用VScroll
事件,必须引入一个变量,防止两个Control反复触发对方的Scroll动作,导致Whosebug异常
请参阅 VScroll 事件处理程序和 synchScroll
布尔字段的使用。
▶ SyncScrollPosition()
方法调用 GetAbsoluteMaxVScroll()
和 GetRelativeScrollDiff()
计算相对滚动值的方法,然后调用 SendMessage 设置要同步的控件的滚动位置。
两者都接受 TextBoxBase
个参数,因为 RichTextBox 派生自这个基础 class,作为 TextBox class,因此您可以对 RichTextBox 和 TextBox 控件使用相同的方法而无需任何更改。
▶ 使用您在此处找到的 SendMessage
声明等。
Private synchScroll As Boolean = False
Private Sub richTextBox1_VScroll(sender As Object, e As EventArgs) Handles RichTextBox1.VScroll
SyncScrollPosition(RichTextBox1, RichTextBox2)
End Sub
Private Sub richTextBox2_VScroll(sender As Object, e As EventArgs) Handles RichTextBox2.VScroll
SyncScrollPosition(RichTextBox2, RichTextBox1)
End Sub
Private Sub SyncScrollPosition(ctrlSource As TextBoxBase, ctrlDest As TextBoxBase)
If synchScroll Then Return
synchScroll = True
Dim infoSource = GetAbsoluteMaxVScroll(ctrlSource)
Dim infoDest = GetAbsoluteMaxVScroll(ctrlDest)
Dim relScrollDiff As Single = GetRelativeScrollDiff(infoSource.nMax, infoDest.nMax, ctrlSource, ctrlDest)
Dim nPos = If(infoSource.nTrackPos > 0, infoSource.nTrackPos, infoSource.nPos)
Dim pt = New Point(0, CType((nPos + 0.5F) * relScrollDiff, Integer))
SendMessage(ctrlDest.Handle, EM_SETSCROLLPOS, 0, pt)
synchScroll = False
End Sub
Private Function GetAbsoluteMaxVScroll(ctrl As TextBoxBase) As SCROLLINFO
Dim si = New SCROLLINFO(SBInfoMask.SIF_ALL)
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
另一个
C#版本:
private bool synchScroll = false;
private void richTextBox1_VScroll(object sender, EventArgs e)
{
SyncScrollPosition(richTextBox1, richTextBox2);
}
private void richTextBox2_VScroll(object sender, EventArgs e)
{
SyncScrollPosition(richTextBox2, richTextBox1);
}
private void SyncScrollPosition(TextBoxBase ctrlSource, TextBoxBase ctrlDest) {
if (synchScroll) return;
synchScroll = true;
var infoSource = GetAbsoluteMaxVScroll(ctrlSource);
var infoDest = GetAbsoluteMaxVScroll(ctrlDest);
float relScrollDiff = GetRelativeScrollDiff(infoSource.nMax, infoDest.nMax, ctrlSource, ctrlDest);
int nPos = infoSource.nTrackPos > 0 ? infoSource.nTrackPos : infoSource.nPos;
var pt = new Point(0, (int)((nPos + 0.5F) * relScrollDiff));
SendMessage(ctrlDest.Handle, EM_SETSCROLLPOS, 0, ref pt);
synchScroll = false;
}
private SCROLLINFO GetAbsoluteMaxVScroll(TextBoxBase ctrl) {
var si = new SCROLLINFO(SBInfoMask.SIF_ALL);
GetScrollInfo(ctrl.Handle, SBParam.SB_VERT, ref si);
return si;
}
private float GetRelativeScrollDiff(int sourceScrollMax, int destScrollMax, TextBoxBase source, TextBoxBase dest) {
float border = source.BorderStyle == BorderStyle.None ? 0F : 1.0F;
return ((float)destScrollMax - dest.ClientSize.Height) / ((float)sourceScrollMax - source.ClientSize.Height - border);
}
声明:
using System.Runtime.InteropServices;
private const int WM_USER = 0x400;
private const int EM_GETSCROLLPOS = WM_USER + 221;
private const int EM_SETSCROLLPOS = WM_USER + 222;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out] ref Point lParam);
[DllImport("user32.dll")]
internal static extern bool GetScrollInfo(IntPtr hwnd, SBParam fnBar, ref SCROLLINFO lpsi);
[StructLayout(LayoutKind.Sequential)]
internal struct SCROLLINFO {
public uint cbSize;
public SBInfoMask fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
public SCROLLINFO(SBInfoMask mask)
{
cbSize = (uint)Marshal.SizeOf<SCROLLINFO>();
fMask = mask; nMin = 0; nMax = 0; nPage = 0; nPos = 0; nTrackPos = 0;
}
}
internal enum SBInfoMask : uint {
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS,
SIF_POSRANGE = SIF_RANGE | SIF_POS | SIF_PAGE
}
internal enum SBParam : int {
SB_HORZ = 0x0,
SB_VERT = 0x1,
SB_CTL = 0x2,
SB_BOTH = 0x3
}