扩展 NumericUpDown 控件






4.89/5 (35投票s)
一个扩展的 NumericUpDown 控件,具有更好的焦点和鼠标滚轮管理。
- GitHub 仓库: https://github.com/nicorac/extended-numericupdown-control
- 下载最新版本: https://github.com/nicorac/extended-numericupdown-control/archive/master.zip
引言
如果您编写过数据录入应用程序,那么您很可能使用过 NumericUpDown
控件。这个控件非常适合提供一个输入数值的字段,并具有上/下按钮和加速自动重复等高级功能。
另一方面,NumericUpDown
并不真正“懂”鼠标。我遇到了一些 bug 和不良行为
- 我需要在获得焦点时选择所有文本(如下所示),但它缺少一些
TextBox
的属性,例如SelectedText
、SelectionStart
、SelectionLength
(一个AutoSelect
属性将很有用)。 - 一些标准事件无法正常工作(如下所示):
MouseEnter
、MouseLeave
。 - 当控件获得焦点时旋转鼠标滚轮会导致其值发生变化。一个用于改变此行为的属性,例如用于上/下键的
InterceptArrowsKeys
,将非常有用。
这就是为什么我决定对其进行子类化,修复这些问题并添加缺失的功能和属性。
缺失的 TextBox 属性
当有人要求我在控件获得焦点时选择所有文本时,我需要一些缺失的 TextBox
属性。
是的,NumericUpDown
暴露了一个 Select(int Start, int Length)
方法,您可以调用它来选择所有文本。乍一看,我附加到 GotFocus
事件来调用 Select(0, x)
,但是,等等…… x 应该用什么值?似乎任何值都可以接受,即使它大于文本长度。好的,我们假设 x=100
并继续。这对于键盘焦点键(如 TAB)非常有效,但对于鼠标来说完全无用:鼠标单击会引发 GotFocus
事件(我在其中选择所有文本),但一旦您释放按钮,就会执行零选择,导致控件没有选择。好的,我想,让我们也在 MouseUp
事件中添加一个 SelectAll
,但这样,用户就无法再进行部分选择;每次释放鼠标按钮时,所有文本都会被选中。我需要知道是否存在部分选择;在 TextBox
中,我可以用 SelectionLength > 0
来测试它,所以需要访问底层的 TextBox
控件。
现在棘手的部分来了:NumericUpDown
是一个复合控件,一个 TextBox
和一个按钮框。通过 Reflector 查看它,我们可以找到保存文本框部分的内部字段。
Friend upDownEdit As UpDownEdit ' UpDownEdit inherits from TextBox
我们将通过底层 Controls()
集合获得对该字段的引用。请注意,我们应该添加一些安全检查,因为将来的 .NET Framework 实现可能会改变。
''' <summary>
''' object creator
''' </summary>
Public Sub New()
MyBase.New()
' get a reference to the underlying UpDownButtons field
' Underlying private type is System.Windows.Forms.UpDownBase+UpDownButtons
_upDownButtons = MyBase.Controls(0)
If _upDownButtons Is Nothing _
OrElse _upDownButtons.GetType().FullName <> _
"System.Windows.Forms.UpDownBase+UpDownButtons" Then
Throw New ArgumentNullException(Me.GetType.FullName & _
": Can't a reference to internal UpDown buttons field.")
End If
' Get a reference to the underlying TextBox field.
' Underlying private type is System.Windows.Forms.UpDownBase+UpDownButtons
_textbox = TryCast(MyBase.Controls(1), TextBox)
If _textbox Is Nothing _
OrElse _textbox.GetType().FullName <> _
"System.Windows.Forms.UpDownBase+UpDownEdit" Then
Throw New ArgumentNullException(Me.GetType.FullName & _
": Can't get a reference to internal TextBox field.")
End If
End Sub
现在我们有了底层的 TextBox
,就可以导出一些缺失的属性了。
<Browsable(False)>
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Public Property SelectionStart() As Integer
Get
Return _textbox.SelectionStart
End Get
Set(ByVal value As Integer)
_textbox.SelectionStart = value
End Set
End Property
最后,我们可以拥有一个完美工作的鼠标管理。
' MouseUp will kill the SelectAll made on GotFocus.
' Will restore it, but only if user have not made
' a partial text selection.
Protected Overrides Sub OnMouseUp(ByVal mevent As MouseEventArgs)
If _autoSelect AndAlso _textbox.SelectionLength = 0 Then
_textbox.SelectAll()
End If
MyBase.OnMouseUp(mevent)
End Sub
Mouse 事件未正确引发
原始的 MouseEnter
和 MouseLeave
事件成对引发:一个 MouseEnter
紧接着一个 MouseLeave
。也许这就是为什么,为了阻止使用它们,它们被标记了 <Browsable(False)>
属性。由于我需要 MouseEnter
事件来更新我的 StatusBar
标题,所以我对这个“bug”进行了一些研究。
如上所述,NumericUpDown
是一个复合控件(下图中的红色矩形),其中包含一个 TextBox
(左侧绿色矩形)和其他一些控件。
“控件”区域是红色和绿色矩形之间的区域;当您用鼠标在该区域上方移动时,您会收到 MouseEnter
事件,然后在绿色矩形内部时收到 MouseLeave
事件。当您离开时也是如此。
现在我们可以访问底层的 TextBox
了,引发这些事件的更好方法是将 MouseEnter
和 MouseLeave
事件重新引发,就像从 TextBox
本身引发一样;这就是 NumericUpDownEx
所做的。
鼠标滚轮管理
NumericUpDown
对鼠标滚轮的管理有时非常烦人。假设您有一个显示某种图表的应用程序,其中有一个顶层对话框(工具箱)供用户更改图表的某些参数。在此对话框中,唯一可以保持焦点的控件是 NumericUpDown
控件。
当用户将焦点放在其中一个控件后,鼠标滚轮将被 NumericUpDown
捕获。当用户滚动鼠标滚轮以滚动图表时,效果是焦点字段的值会改变;这种行为非常烦人。
一种解决方法是销毁控件的 WM_MOUSEWHEEL
消息,但这也会销毁“合法”的滚轮操作。
NumericUpDown
有一个属性,允许 WM_MOUSEWHEEL
消息仅在鼠标指针位于控件上方时通过,从而确保用户正在通过滚轮更改控件的值。
这是通过在 MouseEnter
-MouseLeave
事件中跟踪鼠标状态,然后相应地销毁 WM_MOUSEWHEEL
消息来完成的。
如何使用控件
只需将 NumericUpDownEx.vb 包含在您的项目中,然后像使用标准 NumericUpDown
一样使用该控件。如果您有一个 C# 项目,可以引用 CoolSoft.NumericUpDownEx.dll 程序集,或者最好尝试将代码转换为 C#(这应该不难)。我可以应要求提供 C# 版本。
更新
v1.6 (2016年1月6日)
- 向 ShowUpDownButtonsMode 枚举添加了“Never”值,以始终隐藏 UpDown 旋转器控件。
v1.5 (2014年3月28日)
- 删除了反射代码,现在底层控件仅使用托管代码检索(感谢 JWhattam 的建议)。
v1.4 (2012年12月17日)
-
新的选项,当控件获得焦点时显示上/下按钮(无论鼠标悬停),感谢 Fred Kreppert 的 建议。
v1.3 (2010年3月15日)
- 添加了新的
WrapValue
属性:设置后,如果增量达到Maximum
,Value
将从Minimum
重新开始(反之亦然)。
(由 YosiHakel 在此处 建议的功能) - 清理了 C# 版本。
v1.2 (2010年2月10日)
- 添加了两个新事件
BeforeValueDecrement
和BeforeValueIncrement
,如 andrea@gmi 所建议。这将允许根据控件的当前值给出不同的增量/减量。 - 将控件的 C# 版本添加到 ZIP 文件中。