在开发数据录入应用程序时,经常使用NumericUpDown控件,因为它提供了一个输入数字值的字段,并带有上下按钮和加速自动重复等高级功能。然而,NumericUpDown控件并不总是对鼠标操作友好。在实际使用中,遇到了一些bug和不良行为,例如在获取焦点时需要选择所有文本,但它缺少一些TextBox属性,如SelectedText、SelectionStart、SelectionLength(AutoSelect属性将非常有用)。此外,一些标准事件没有正确触发,如MouseEnter、MouseLeave。当控件获得焦点时,鼠标滚轮旋转会导致其值改变,一个可以改变这种行为的属性将非常有用。
为了解决这些问题,决定扩展NumericUpDown控件,修复这些问题并添加缺失的特性和属性。首先,需要一些缺失的TextBox属性。当被要求在控件获得焦点时选择所有文本时,遇到了这个问题。NumericUpDown确实提供了一个Select(int Start, int Length)方法,可以调用以选择所有文本。但是,当尝试将Select(0, x)附加到GotFocus事件时,发现任何值都被接受,即使大于文本长度。这在使用键盘焦点键(如TAB)时效果很好,但使用鼠标时完全无用:鼠标点击会触发GotFocus事件(在这里选择所有文本),但一旦释放按钮,零选择就会完成,留下没有选择的控件。
接下来是棘手的部分:NumericUpDown是一个复合控件,包含一个TextBox和一个按钮框。通过Reflector查看它的内部,可以找到包含文本框部分的内部字段:VB.NET中的Friend upDownEdit。UpDownEdit继承自TextBox。将使用Controls()集合来获取这个字段的引用。需要注意的是,应该添加一些安全检查,因为未来的.NET Framework实现可能会发生变化。
现在已经获得了TextBox的引用,可以导出一些缺失的属性了。例如,可以添加一个SelectionStart属性,它允许获取或设置文本选择的起始位置。此外,还可以添加一个OnMouseUp方法,以确保在鼠标按钮释放时,如果用户没有进行部分文本选择,则恢复SelectAll。
接下来,讨论了MouseEnter和MouseLeave事件没有正确触发的问题。原始的NumericUpDown控件中的这些事件是成对触发的:一个MouseEnter事件紧随一个MouseLeave事件。也许这就是为什么它们被标记为
NumericUpDown是一个复合控件(下图中的红色矩形),包含一个TextBox(左侧绿色矩形)和其他一些控件。当用鼠标在红色和绿色矩形之间移动时,将收到MouseEnter事件,然后在绿色矩形内部收到MouseLeave事件。离开时也是如此。更好的方法是重新触发这些事件,就像它们是从TextBox本身触发的一样;这就是NumericUpDownEx所做的。
最后,讨论了NumericUpDown对鼠标滚轮的管理,有时这确实很烦人。假设有一个应用程序,它显示某种图表,并在顶部有一个对话框(工具箱),让用户可以更改图表的一些参数。在对话框中,唯一可以保持焦点的控件是NumericUpDown。当用户将焦点放在其中一个控件上时,鼠标滚轮被NumericUpDown捕获。当用户滚动以查看图表时,效果是改变了焦点字段的值;这种行为确实很烦人。
一个解决方案是杀死控件的WM_MOUSEWHEEL消息,但这将杀死所有“合法”的滚动。NumericUpDown有一个属性,允许WM_MOUSEWHEEL消息只在鼠标指针在控件上方时通过,确保用户滚动是为了更改控件的值。这是通过在MouseEnter-MouseLeave事件中跟踪鼠标状态,然后相应地杀死WM_MOUSEWHEEL消息来实现的。