65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展 NumericUpDown 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (35投票s)

2008年11月12日

CPOL

5分钟阅读

viewsIcon

269553

downloadIcon

2819

一个扩展的 NumericUpDown 控件,具有更好的焦点和鼠标滚轮管理。

引言

如果您编写过数据录入应用程序,那么您很可能使用过 NumericUpDown 控件。这个控件非常适合提供一个输入数值的字段,并具有上/下按钮和加速自动重复等高级功能。

另一方面,NumericUpDown 并不真正“懂”鼠标。我遇到了一些 bug 和不良行为

  • 我需要在获得焦点时选择所有文本(如下所示),但它缺少一些 TextBox 的属性,例如 SelectedTextSelectionStartSelectionLength(一个 AutoSelect 属性将很有用)。
  • 一些标准事件无法正常工作(如下所示):MouseEnterMouseLeave
  • 当控件获得焦点时旋转鼠标滚轮会导致其值发生变化。一个用于改变此行为的属性,例如用于上/下键的 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 事件未正确引发

原始的 MouseEnterMouseLeave 事件成对引发:一个 MouseEnter 紧接着一个 MouseLeave。也许这就是为什么,为了阻止使用它们,它们被标记了 <Browsable(False)> 属性。由于我需要 MouseEnter 事件来更新我的 StatusBar 标题,所以我对这个“bug”进行了一些研究。

如上所述,NumericUpDown 是一个复合控件(下图中的红色矩形),其中包含一个 TextBox(左侧绿色矩形)和其他一些控件。

“控件”区域是红色和绿色矩形之间的区域;当您用鼠标在该区域上方移动时,您会收到 MouseEnter 事件,然后在绿色矩形内部时收到 MouseLeave 事件。当您离开时也是如此。

现在我们可以访问底层的 TextBox 了,引发这些事件的更好方法是将 MouseEnterMouseLeave 事件重新引发,就像从 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 属性:设置后,如果增量达到 MaximumValue 将从 Minimum 重新开始(反之亦然)。
    (由 YosiHakel 在此处 建议的功能)
  • 清理了 C# 版本。

v1.2 (2010年2月10日)

  • 添加了两个新事件 BeforeValueDecrementBeforeValueIncrement,如 andrea@gmi 所建议。这将允许根据控件的当前值给出不同的增量/减量。
  • 将控件的 C# 版本添加到 ZIP 文件中。
© . All rights reserved.