我找到了这个问题的部分解决方案。假设 ListView 正在被扩展/子类化。首先,从user32.dll导入一个函数; GetScrollInfo,如下:
Declare Function GetScrollInfo Lib "user32" Alias "GetScrollInfo" (ByVal hWnd As IntPtr, _
ByVal n As Integer, ByRef lpScrollInfo As SCROLLINFO) As Integer
Structure SCROLLINFO
Dim cbSize As Integer
Dim fMask As Integer
Dim nMin As Integer
Dim nMax As Integer
Dim nPage As Integer
Dim nPos As Integer
Dim nTrackPos As Integer
End Structure
Private Const SB_HORZ As Integer = 0
Private Const SB_VERT As Integer = 1
这可用于获取窗口的滚动位置作为 Y 偏移量。滚动位置在 Fantasy-scroll-units 中定义,只要它们是 32 位整数,它们是可定义的,但是被调用者希望它们是。 “滚动动作”也不必是欧几里得,但单位用于调整滚动条的大小;例如可拖动元素的大小或“页面大小”定义为以“滚动单位”为单位的窗口大小除以总滚动条的大小,具有预先设置的硬编码最小大小,但每次单击或拖动或鼠标滚轮移动滚动由使用滚动条的元素编码。
这导致了SCROLLINFO 结构的定义。它有七名成员;
-
nMin 和 nMax。 nMin <= nPos <= nMax 被scrollBar 自动夹紧。当滚动条完全位于顶部时,则为nPos = nMin。完全在底部,nPos = nMax。
-
cbSize 是这个结构的大小。使用 win32 api 时通常为 cbSize = 28。
-
fMask 可用于仅检索部分值。要全部检索,fMask = 7。价值观:
-
0x01:检索nMin、nMax
-
0x02:检索nPage
-
0x04:检索 nPos 和 nTrackPos
-
nPage 表示滚动旋钮的(相对)大小:nPage / (nMax - nMin) 将是相对于 y 滚动条的条大小的高度。
-
nPos 是滚动条的当前位置。
-
nTrackPos 是相同的,但在单击和拖动操作期间会更新,允许您在拖动滚动条时(而不是默认的;当您释放它时)移动表单元素,就像大多数文本编辑器一样。
第一个技巧是弄清楚虚数单位是如何映射到像素的。幸运的是,对于ListView,这看起来相当简单:以下定义“足够接近”;
Public Function getScrollPos() As Integer
Dim si As SCROLLINFO = Me.getScrollInfo()
Dim x As Integer = GetScrollInfo(Me.Handle, SB_VERT, si)
' The Windows Scrollbar magicunit is the same as pixels, off by exactly one window height.
' Adding nPage gets you a pixel based location.
Return si.nPos + si.nPage
End Function
接下来,我们必须使用此信息并设置listView 的 Y 值。这是困难的部分;在基本的 Windows 窗体中,没有现成的方法用于此任务。但是,每个项目都具有获取其 Y 位置的功能。但是,简单地使用Me.items(Y/17)(注意;17 是默认高度)的幼稚方法根本不起作用,因为Me.Items 成员变量没有随显示排序。
Public Sub setScrollPos(ByVal y As Integer)
Dim si As SCROLLINFO = Me.getScrollInfo()
Dim x As Integer = GetScrollInfo(Me.Handle, SB_VERT, si)
If (y > si.nMax) Then
y = Math.Max(si.nMax, 0)
End If
Dim itemHeight As Integer = Me.Items(0).GetBounds(ItemBoundsPortion.Entire).Height
Dim iClosest = 0
Dim iTmp = 0
Dim delta = Integer.MaxValue
' "Stupidly clever" solution by simply trying all items out and finding out which one is closest to the Y value.
' Implemented as there is no "sorted index" exposed by ListViewItemCollection. (No idea why...)
If Me.Items.Count > 0 Then
For i As Integer = 0 To Me.Items.Count - 1 Step 1
iTmp = Math.Abs(Me.Items.Item(i).Position.Y - y)
If iTmp < delta Then
delta = iTmp
iClosest = i
End If
Next 'i
End If
Me.TopItem = Me.Items.Item(iClosest)
Me.Invalidate()
End Sub
在这里,遍历每个项目,找出最适合我们测量的 y 坐标,然后将其设置为顶部。从某种意义上说,解决方案是“愚蠢的”,因为它执行了表单为了正确呈现自身而无论如何都需要执行的计算:它需要知道其列表元素呈现的顺序。它也不是相当 em> 正确:如果顶部的预期可见行是(部分)“组分隔符”,那么我们的 setScrollPos 函数将改为找到最近的 ListViewItem 并将其显示在顶部。它也不能处理任何子线的移动,因此可以通过四舍五入到整行来偏离(最多)半个行高。