缘由:
在设计程序过程中,经常用到滚动条控件,取值范围经常会超过 0-32767 的范围,如果按比例映射,又很需要最小变化值为1的情况,采用第三方控件固然可以解决这个问题,却又通常需要额外的动态链接库的支持。
设想利用标准控件来扩充,使取值范围可以是长整数。
创建一个虚拟滚动条类 VirtualScroll.cls 如下:
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True Persistable = 0 'NotPersistable DataBindingBehavior = 0 'vbNone DataSourceBehavior = 0 'vbNone MTSTransactionMode = 0 'NotAnMTSObject END Attribute VB_Name = "VirtualScroll" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = True Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Attribute VB_Ext_KEY = "SavedWithClassBuilder6" ,"Yes" Attribute VB_Ext_KEY = "Top_Level" ,"Yes" ' 利用标准滚动条设计的虚拟滚动条 ' 滚动范围为长整数,可以是负数 ' ' 使用方法: ' (1)在工程中添加类 VirtualScroll 和标准滚动条控件 mScro ' (2)定义虚拟滚动条 vScro(或其它名称亦可): ' Dim WithEvents vScro As VirtualScroll ' (3)关联控件和虚拟滚动条 ' Associate(ScrollControl As VScrollBar, Optional Min As Long = 0, Optional Max As Long = 0, _ ' Optional Value As Long = 0, Optional SmallChange As Long = 0, Optional LargeChange As Long = 0) ' Min, Max, Value 均为 0 时,从控件中获取所有值(包括小变化值和大变化值) ' (4)事件处理: ' ' 滚动值改变事件(单击“箭头”、“滚动块和上箭头之间的区域”或“滚动块和下箭头之间的区域”时发生) ' Private Sub vScro_Change() ' Debug.Print "Virtual Change:" & vScro.Value ' End Sub ' ' ' 滚动事件(拖动滚动块时发生) ' Private Sub vScro_Scroll() ' Debug.Print "Virtual Scroll:" & vScro.Value ' End Sub ' (5)属性设置和获取: ' vScro.Min = 最小值 ' vScro.Max = 最大值 ' vScro.Value = 当前值 ' vScro.SmallChange = 小变化值 ' vScro.LargeChange = 大变化值 ' vScro.Left = 控件的左侧坐标 ' vScro.Top = 控件的上端坐标 ' vScro.Width = 控件的宽度 ' vScro.Height = 控件的高度 ' vScro.Enabled = 控件启用状态 ' vScro.Visible = 控件可见状态 ' (6)方法: ' vScro.SetFocus = 设置控件获得焦点 ' Associate(ScrollControl As VScrollBar, Optional Min As Long = 0, Optional Max As Long = 0, _ ' Optional Value As Long = 0, Optional SmallChange As Long = 0, Optional LargeChange As Long = 0) ' 关联控件和虚拟滚动条 ' Min, Max, Value 均为 0 时,从控件中获取所有值(包括小变化值和大变化值) ' ' 注意: ' (1)设置数值超出范围时,自动调整到最接近的边界 ' (2)小变化值和大变化值都必须大于 1,如果小于 1 则自动调整到合法值 ' (3)小变化、大变化和设定值都可以准确表现(比如范围为 -65536 和 150000 之间,小变化=1,大变化=10,则单击箭头将变化 1,按上下段将变化 10, 这是通过变化大小测定的) ' (4)拖动值则只能通过计算大致得出(无法一一对应) ' (5)最小值和最大值可以随意设置, 但仅支持到长整数(-2147483648 和 2147483647 之间的值), 设置边界时,同时校正虚拟滚动条的数值 ' Option Explicit '要引发该事件,请遵循下列语法使用 RaiseEvent: 'RaiseEvent Change[(arg1, arg2, ... , argn)] Public Event Change() '要引发该事件,请遵循下列语法使用 RaiseEvent: 'RaiseEvent Scroll[(arg1, arg2, ... , argn)] Public Event Scroll() '保持属性值的局部变量 Private mEnabled As Boolean ' 控件的启用值 Private mVisible As Long ' 控件的可见值 Private mLarge As Long ' 大变化值 Private mMax As Long ' 最大值 Private mMin As Long ' 最小值 Private mSmall As Long ' 小变化值 Private mValue As Long ' 实际值 Private mIsNotEvent As Boolean ' 检测是否事件(执行值改变的动作 或 仅设置滚动条的值) Private WithEvents mScrollBar As VScrollBar ' 滚动条对象 Attribute mScrollBar.VB_VarHelpID = -1 Private ScroScale As Double ' 转换比例 Private ScroMin As Long ' 滚动条最小值 Private ScroMax As Long ' 滚动条最大值 Private ScroValue As Long ' 滚动条当前值 Private ScroSmall As Long ' 小变化值 Private ScroLarge As Long ' 大变化值 Private MinBound As Long ' 小边界(设置此值方可处理最小值大于最大值的情况) Private MaxBound As Long ' 大边界(设置此值方可处理最小值大于最大值的情况) Private IsAssociated As Boolean ' 是否已经关联 Private Sub Class_Initialize() IsAssociated = False mMin = 0 mMax = 65536 mSmall = 1 mValue = 0 MinBound = 0 MaxBound = 65536 mIsNotEvent = False Set mScrollBar = Nothing mLarge = 10 End Sub Private Sub Class_Terminate() Set mScrollBar = Nothing End Sub Private Sub mScrollBar_Change() ' 接管滚动条的改变事件 Change End Sub Private Sub mScrollBar_Scroll() ' 接管滚动条的滚动事件 Scroll End Sub Public Sub SetFocus() If IsAssociated Then mScrollBar.SetFocus End If End Sub Public Sub Associate(ScrollControl As VScrollBar, Optional Min As Long = 0, Optional Max As Long = 0, Optional Value As Long = 0, Optional SmallChange As Long = 0, Optional LargeChange As Long = 0) Set mScrollBar = ScrollControl ScroMin = mScrollBar.Min ScroMax = mScrollBar.Max ScroValue = mScrollBar.Value ScroSmall = mScrollBar.SmallChange ScroLarge = mScrollBar.LargeChange If Min = 0 And Max = 0 And Value = 0 Then ' 全部使用控件的设置 mMin = ScroMin mMax = ScroMax mValue = ScroValue mSmall = ScroSmall mLarge = ScroLarge ScroScale = 1 MinBound = MinValue(Min, Max) MaxBound = MaxValue(Min, Max) Else mMin = Min mMax = Max MinBound = MinValue(Min, Max) MaxBound = MaxValue(Min, Max) If Value < MinBound Then ' 指定值小于最小值时取最小值 Value = MinBound End If If Value > MaxBound Then ' 指定值大于最大值时取最大值 Value = MaxBound End If If SmallChange <= 0 Then SmallChange = 1 End If If LargeChange <= 0 Then LargeChange = 1 End If mValue = Value mSmall = SmallChange mLarge = LargeChange CalcScale End If IsAssociated = True End Sub Private Sub CalcScale() Dim lngValue1 As Long Dim lngValue2 As Long Dim newScroValue As Long If Abs(mMax - mMin) > 32767 Then ' 数值范围大小超过标准控件的数值范围大小 ScroScale = CDbl(Abs(mMax - mMin)) / 32767# ' 计算比例 If mMin <= mMax Then ScroMin = 0 ' 控件的最小值为 0 ScroMax = 32767 ' 控件的最大值为 32767(最大) Else ScroMin = 32767 ' 控件的最小值为 0 ScroMax = 0 ' 控件的最大值为 32767(最大) End If newScroValue = Fix((mValue - MinBound) / ScroScale) ' 根据数值计算控件的数值 lngValue1 = Fix(mSmall / ScroScale) ' 根据小变化值计算控件的小变化值 If lngValue1 < 1 Then lngValue1 = 1 ' 控件的小变化值最小为 1 lngValue2 = Fix(mLarge / ScroScale) ' 根据大变化值计算控件的大变化值 If lngValue2 < 1 Then lngValue2 = 1 ' 控件的大变化值最小为 1 If lngValue2 = lngValue1 Then ' 当计算出的控件的小变化值和大变化值相同时 If mSmall <> mLarge Then ' 如果虚拟的小变化值和大变化值不同 lngValue2 = lngValue1 + Sgn(mLarge - mSmall) ' 则在原值的基础上调整大变化值为(+-1) If lngValue2 < 1 Then ' 但仍然都不能小于 1(大变化值小于小变化值时出现) lngValue1 = lngValue1 + 1 lngValue2 = lngValue2 + 1 End If End If End If ScroSmall = lngValue1 ScroLarge = lngValue2 Else ScroScale = 1 ' 此处将控件的范围调整到 0-差值 之间(即最小/最大值总是 0-n 或者 n-0, n=0-32767) If mMin <= mMax Then ScroMin = 0 ScroMax = mMax - mMin ' 范围为超出时仅调整最大值和最小值的范围 newScroValue = mValue - mMin Else ScroMin = mMin - mMax ScroMax = 0 newScroValue = mValue - mMax End If ScroSmall = mSmall ScroLarge = mLarge End If mScrollBar.Min = ScroMin ' 将计算的值赋给控件 mScrollBar.Max = ScroMax mScrollBar.SmallChange = ScroSmall mScrollBar.LargeChange = ScroLarge SetScrollValue newScroValue End Sub Private Sub SetScrollValue(Value As Long) ' 用这个字程序设置滚动条的值不再进行转换 ' 在事件中检测到为非事件时不做事件处理 ' 在控件的事件中应该调用本类的 Scroll Dim ScroMinValue As Long Dim ScroMaxValue As Long ScroMinValue = MinValue(ScroMin, ScroMax) ScroMaxValue = MaxValue(ScroMin, ScroMax) If Value < ScroMinValue Then Value = ScroMinValue End If If Value > ScroMaxValue Then Value = ScroMaxValue End If ' 边界附近的处理(在超出 32767 时方能发生) ' 如果控件的值达到边界,而虚拟滚动条的值尚未达到边界 ' 则需要将控件的值移动到于边界一个单位的距离 ' 以免无法通过箭头或点击上下的空白区域改变虚拟滚动条的值 If Value = ScroMaxValue And mValue < MaxBound Then Value = ScroMaxValue - 1 ElseIf Value = ScroMinValue And mValue > MinBound Then Value = ScroMinValue + 1 End If ' 这里一定要强制设置,因为总是可能与滚动条的实际值不同 ScroValue = Value If mScrollBar.Value <> Value Then ' 控件的当前值和要赋给的值不同时 mIsNotEvent = True ' 设置完后将产生事件,设置标志不进行回设 mScrollBar.Value = Value DoEvents mIsNotEvent = False End If End Sub Private Sub ChangeOrScroll(IsChange As Boolean) ' 本字程序用于将滚动条的值换算成虚拟值并重设滚动条的值 Dim CurScrollValue As Long Dim newScroValue As Long If (Not IsAssociated) Or mIsNotEvent Then ' 未关联,或者: ' 根据设定的值设置滚动位置时,会发生滚动或者修改事件 ' 此时应该会调用本程序进行校正 ' 非来自于键盘和鼠标事件,因此不再设置滚动条的状态 ' 表明仅仅设置滚动条的值 ' 不再重新计算和设定 Exit Sub End If CurScrollValue = mScrollBar.Value If CurScrollValue = ScroValue Then Exit Sub ' 不会发生 If Abs(CurScrollValue - ScroValue) = ScroSmall Then mValue = mValue + Sgn(CurScrollValue - ScroValue) * mSmall ElseIf Abs(CurScrollValue - ScroValue) = ScroLarge Then mValue = mValue + Sgn(CurScrollValue - ScroValue) * mLarge ElseIf CurScrollValue = ScroMin Then ' 到达边界时,通过计算可能出现无法达到边界的情况 mValue = mMin ElseIf CurScrollValue = ScroMax Then ' 到达边界时,通过计算可能出现无法达到边界的情况 mValue = mMax Else mValue = Fix(CurScrollValue * ScroScale) + MinBound End If If mValue < MinBound Then mValue = MinBound If mValue > MaxBound Then mValue = MaxBound newScroValue = Fix((mValue - MinBound) / ScroScale) SetScrollValue newScroValue If IsChange Then ' 激发改变事件 RaiseEvent Change Else RaiseEvent Scroll ' 激发滚动事件 End If End Sub Public Sub Change() ChangeOrScroll True End Sub Public Sub Scroll() ChangeOrScroll False End Sub 'Public Property Let IsNotEvent(ByVal ManualAction As Boolean) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.IsNotEvent = 5 ' mIsNotEvent = ManualAction 'End Property 'Public Property Get IsNotEvent() As Boolean '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.IsNotEvent ' IsNotEvent = mIsNotEvent 'End Property Public Property Let Value(ByVal ScrollValue As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Value = 5 Dim newScroValue As Long If (Not IsAssociated) Or ScrollValue = mValue Then Exit Property End If mValue = ScrollValue If (mValue < MinBound) Then mValue = MinBound If (mValue > MaxBound) Then mValue = MaxBound newScroValue = Fix((mValue - MinBound) / ScroScale) SetScrollValue newScroValue End Property Public Property Get Value() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Value Value = mValue End Property Private Sub CalcChange() Dim lngValue1 As Long Dim lngValue2 As Long If Abs(ScroScale - 1) > 0.00001 Then lngValue1 = Fix(mSmall / ScroScale) If lngValue1 < 1 Then lngValue1 = 1 ScroSmall = lngValue1 lngValue2 = Fix(mLarge / ScroScale) If lngValue2 = lngValue1 Then If mSmall <> mLarge Then lngValue2 = lngValue1 + Sgn(mLarge - mSmall) End If End If ScroLarge = lngValue2 Else ScroSmall = mSmall ScroLarge = mLarge If ScroSmall < 1 Then ScroSmall = 1 If ScroLarge < ScroSmall Then ScroLarge = ScroSmall End If mScrollBar.SmallChange = ScroSmall mScrollBar.LargeChange = ScroLarge End Sub Public Property Let Enabled(ByVal EnableValue As Boolean) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Enabled = True If IsAssociated Then mEnabled = EnableValue mScrollBar.Enabled = mEnabled End If End Property Public Property Get Enabled() As Boolean '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Enabled If IsAssociated Then mEnabled = mScrollBar.Enabled Enabled = mEnabled End If End Property Public Property Let Visible(ByVal VisibleValue As Boolean) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Visible = True If IsAssociated Then mVisible = VisibleValue mScrollBar.Visible = mVisible End If End Property Public Property Get Visible() As Boolean '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Visible If IsAssociated Then mVisible = mScrollBar.Visible Visible = mVisible End If End Property Public Property Let Left(ByVal mLeft As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Left = 5 If IsAssociated Then mScrollBar.Left = mLeft End If End Property Public Property Get Left() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Left If IsAssociated Then Left = mScrollBar.Left End If End Property Public Property Let Top(ByVal mTop As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Top = 5 If IsAssociated Then mScrollBar.Top = mTop End If End Property Public Property Get Top() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Top If IsAssociated Then Top = mScrollBar.Top End If End Property Public Property Let Width(ByVal mWidth As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Width = 5 If IsAssociated Then mScrollBar.Width = mWidth End If End Property Public Property Get Width() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Width If IsAssociated Then Width = mScrollBar.Width End If End Property Public Property Let Height(ByVal mHeight As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Height = 5 If IsAssociated Then mScrollBar.Height = mHeight End If End Property Public Property Get Height() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Height If IsAssociated Then Height = mScrollBar.Height End If End Property Public Property Let Min(ByVal ScrollMin As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Min = 5 ' 单独设置时,不能让最小值大于当前最大值 If (Not IsAssociated) Or ScrollMin = mMin Then Exit Property End If mMin = ScrollMin MinBound = MinValue(mMin, mMax) MaxBound = MaxValue(mMin, mMax) If mValue < MinBound Then mValue = MinBound End If If mValue > MaxBound Then mValue = MaxBound End If CalcScale End Property Public Property Get Min() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Min Min = mMin End Property Public Property Let Max(ByVal ScrollMax As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.Max = 5 ' 单独设置时,不能让最大值小于当前最小值 If (Not IsAssociated) Or ScrollMax = mMax Then Exit Property End If mMax = ScrollMax MinBound = MinValue(mMin, mMax) MaxBound = MaxValue(mMin, mMax) If mValue < MinBound Then mValue = MinBound End If If mValue > MaxBound Then mValue = MaxBound End If CalcScale End Property Public Property Get Max() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.Max Max = mMax End Property Public Property Let SmallChange(ByVal ScrollSmall As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.LargeChange = 5 If (Not IsAssociated) Or ScrollSmall = mSmall Then Exit Property End If mSmall = ScrollSmall CalcChange End Property Public Property Get SmallChange() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.SmallChange SmallChange = mSmall End Property Public Property Let LargeChange(ByVal ScrollLarge As Long) '向属性指派值时使用,位于赋值语句的左边。 'Syntax: X.LargeChange = 5 If (Not IsAssociated) Or ScrollLarge = mLarge Then Exit Property End If mLarge = ScrollLarge CalcChange End Property Public Property Get LargeChange() As Long '检索属性值时使用,位于赋值语句的右边。 'Syntax: Debug.Print X.LargeChange LargeChange = mLarge End Property Private Function MinValue(a As Variant, b As Variant) As Variant MinValue = IIf(a < b, a, b) End Function Private Function MaxValue(a As Variant, b As Variant) As Variant MaxValue = IIf(a > b, a, b) End Function
VB测试方式:
(1)在VB窗体中添加一个滚动条控件,名称为mScro,两个命令按钮 Command1 和 Command2, 在工程中添加虚拟滚动条类 VirtualScroll.cls
(2)窗体代码:
Option Explicit Dim WithEvents vScro As VirtualScroll Private Sub Command1_Click() vScro.Value = 12345 Debug.Print "Virtual => Min: " & vScro.Min & ", Max: " & vScro.Max & ", Value: " & vScro.Value End Sub Private Sub Command2_Click() Debug.Print "Control => Min: " & mScro.Min & ", Max: " & mScro.Max & ", Value: " & mScro.Value Debug.Print "Virtual => Min: " & vScro.Min & ", Max: " & vScro.Max & ", Value: " & vScro.Value End Sub Private Sub Form_Load() Set vScro = New VirtualScroll vScro.Associate mScro, 65536, -65536, 65536, 1, 10 vScro.Min = 32768 End Sub Private Sub vScro_Change() Debug.Print "Virtual Change:" & vScro.Value End Sub Private Sub vScro_Scroll() Debug.Print "Virtual Scroll:" & vScro.Value End Sub