获取和设置 winForms ListView 滚动位置
Posted
技术标签:
【中文标题】获取和设置 winForms ListView 滚动位置【英文标题】:Getting and setting a winForms ListView scroll position 【发布时间】:2020-05-06 14:10:53 【问题描述】:我该怎么做:
检索 ListView 的当前滚动位置作为基于像素的坐标? (例如:如果 ListView 一直到底部,那么这个函数应该返回listHeight - viewPortLength
的等价物。
将ListView的当前滚动位置设置为所述基于像素的坐标,导致ListView跳转到这个位置?
在 vb.net 或 C#.net 托管代码中执行此操作?
使用“组”处理 ListView?
这里可以假设listView
是一个简单的纯文本列表视图(不是大/小图片版本)。
虽然有很多答案指向使用 EnsureVisible
函数,但所有这些都是随机跳转以确保所指示的项目可见。如果需要某个“滚动位置”,并且事先不知道可见项应该是什么(因为基础数据可能会更改),那么这种方法几乎没有用处。
我还发现了使用 win32-api 的建议,特别是使用 setScrollInfo
和 setScrollWindowEx
。不幸的是,setScrollInfo
只是真正短暂地修改了滚动条本身并保持窗口内容不变,并且只对那些实现自己的特殊滚动行为的人真正有用。 setScrollWindowEx
理论上应该通过调整视口和在新内容中进行 blitting 来工作,但是当与此对象一起使用时,该函数将愉快地报告它在不做任何事情的情况下成功并报告它已经重绘了一个大小为矩形[0, 0]
位于 [0, 0]
位置,以 [w,h],[x,y]
表示法。换句话说,ListView 的内部状态不是很容易访问。
【问题讨论】:
【参考方案1】:我找到了这个问题的部分解决方案。假设 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
并将其显示在顶部。它也不能处理任何子线的移动,因此可以通过四舍五入到整行来偏离(最多)半个行高。
【讨论】:
以上是关于获取和设置 winForms ListView 滚动位置的主要内容,如果未能解决你的问题,请参考以下文章
C# winform程序中 如何获取和设置dataGridView1的垂直滚动条当前位置?