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

NumberBox 类用于数字输入/显示

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020年4月8日

CPOL

4分钟阅读

viewsIcon

11483

downloadIcon

398

一个 NumberBox 类,用于数字输入、显示、范围限制和按键验证,包括二进制。

引言

NumberBox 控件提供了一个方便的单一控件类,用于以多种格式输入和显示数字。此类根据指定的 SNFS 验证按键。此类还会在“Leave”事件触发时执行范围限制检查。它支持 SNFS 类型 'E'、'F'、'D'、'N' 和 'X'。引入了一种新的 'B' 类型用于二进制。用户可以一种格式写入值,程序可以用另一种格式检索值。

首次编译后,NumberBox 控件将显示在您的工具箱中,您可以像其他控件一样将其拖放到 GUI 设计中。

背景

我编写了大量的 GUI,用于直接连接到芯片。这些 GUI 需要以多种格式输入或显示数字,从双精度浮点数到十六进制和二进制,所有这些都具有各种大小和宽度。通常,数字会以二进制形式从目标读取,但我希望将其以十进制形式显示给用户。我厌倦了反复编写这些转换,所以懒惰的程序员本能让我决定创建一个可以完成所有这些工作的类。

Using the Code

演示窗口

下面是该类的演示窗口。左侧面板是您输入数字的地方,按键验证取决于指定的 SNFS。当控件 leave 事件触发时,值将被写回左侧面板,显示将匹配 SNFS,并且右侧面板控件将被更新为新值,进行范围限制并以其各自的 SNFS 格式显示。

此窗口的主要代码如下所示

using System;
using System.Windows.Forms;

namespace NumberBoxDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void numberBox_Leave(object sender, EventArgs e)
        {
            var nb = (NumberBox)sender;

            foreach (var c in panel2.Controls)
                if (c.GetType() == typeof(NumberBox))
                {
                    var box = (NumberBox)c;
                    if (box.Snfs.StartsWith("D")) box.ValueAsInt64 = nb.ValueAsInt64;
                    if (box.Snfs.StartsWith("F")) box.ValueAsFloat = nb.ValueAsFloat;
                    if (box.Snfs.StartsWith("E")) box.ValueAsDouble = nb.ValueAsDouble;
                    if (box.Snfs.StartsWith("N")) box.ValueAsDouble = nb.ValueAsDouble;
                    if (box.Snfs.StartsWith("X")) box.ValueAsInt64 = nb.ValueAsInt64;
                    if (box.Snfs.StartsWith("B")) box.ValueAsByte = nb.ValueAsByte;
                }
        }
    }
}

作为一名懒惰的程序员,我喜欢为多个相同类型的控件编写一个事件处理程序。numberBox_Leave 事件处理程序已连接到左侧面板中的所有 NumberBox 控件。当事件触发时,事件处理程序会遍历右侧面板中的所有控件,如果控件是 NumberBox,则会根据其 SNFS 类型更新该控件。

NumberBox

NumberBox 类派生自 TextBoxTextBoxText 字段中的文本是值的实际存储位置。没有其他内部或私有位置。你看到的就是你得到的。

已添加以下属性

  • ValueAsByte
  • ValueAsDouble
  • ValueAsFloat
  • ValueAsInt16
  • ValueAsInt32
  • ValueAsInt64
  • ValueAsSByte
  • ValueAsUInt16
  • ValueAsUInt32
  • ValueAsUInt64
  • 最大值
  • 最小值
  • 前缀
  • 后缀
  • Snfs

ValueAs... 属性使用正确的类型转换来获取/设置 Text 字符串。

Min/Max 属性获取/设置应用于数字的范围限制。

Snfs 属性是 Microsoft 文档中定义的标准数字格式字符串。我添加了一个新的格式 'B' 用于二进制数字。

NumberBox 代码流程

实例化 NumberBox 控件后,当设置 SNFS 时,会为特定的 SNFS 创建一个正则表达式。此正则表达式 '_pattern' 用于在控件的 '_KeyDown' 和 '_KeyPress' 事件中验证按键。

当用户离开控件时,所有神奇的事情都会发生

  • 控件从 NumberBox 读取文本
  • 移除任何前缀或后缀
  • 根据 SNFS 格式将其转换为双精度浮点数
  • 根据 Min/Max 值进行范围限制
  • 以正确的格式将其写回控件。

正如主演示程序所示,可以触发 'Leave' 事件来执行任何其他所需的操作。在此演示中,'Leave' 事件会更新右侧面板中的 8 个 NumberBox。这两个 leave 事件会按顺序调用,控件的事件先触发,主程序的事件最后触发(标准的 C# 事件链)。

正则表达式生成器

控件的关键部分是构建正则表达式,以在键入控件时验证按键。代码如下所示。一个关键点是用于将二进制添加到 SNFS 系列的 'B' 行。

        private string BuildRegularExpression()
        {
            if (_snfsEmpty) return string.Empty;

            // start of line anchor
            var str = "^";
            var len = LengthParameter(_snfs);

            // add in prefix
            if (!_prefixEmpty)
                str = Prefix.Aggregate(str, (current, c) => current + ("[" + c + "]"));

            // D4 => -?\d\d?\d?\d?\d?
            if (_snfs.StartsWith("D", StringComparison.OrdinalIgnoreCase))
            {
                str += "-?";
                if (len == 0) len = 4;
                for (var i = 0; i < len; ++i) str += @"\d?";
            }
            // E => ^-?\d*[.,]?\d?\d?\d?\d?\d?\d?[Ee]?[+-]?\d?\d?\d?
            if (_snfs.StartsWith("E", StringComparison.OrdinalIgnoreCase))
            {
                str += @"-?\d*[.,]?";
                if (len == 0) len = 6;  // SNFS default length
                for (var i = 0; i < len; ++i) str += @"\d?";
                str += @"[Ee]?[+-]?\d?\d?\d?";
            }
            if (_snfs.StartsWith("F", StringComparison.OrdinalIgnoreCase))
            {
                str += @"-?\d*[.,]?";
                if (len == 0) len = 2;  //SNFS default length
                for (var i = 0; i < len; ++i) str += @"\d?";
            }
            if (_snfs.StartsWith("N", StringComparison.OrdinalIgnoreCase))
            {
                str += @"-?\d*[., ]?";
                if (len == 0) len = 2;  //SNFS default length
                for (var i = 0; i < len; ++i) str += @"\d?";
            }
            if (_snfs.StartsWith("X", StringComparison.OrdinalIgnoreCase))
            {
                if (len == 0) len = 2;  //SNFS default length
                for (var i = 0; i < len; ++i) str += @"[0-9A-Fa-f]?";
            }
            if (_snfs.StartsWith("B", StringComparison.OrdinalIgnoreCase))
            {
                if (len == 0) len = 1;  //SNFS default length
                for (var i = 0; i < len; ++i) str += @"[0-1]?";
            }

            // add suffix
            if (!_suffixEmpty)
                str = _suffix.Aggregate(str, (current, c) => current + ("[" + c + "]"));

            // and the end of line anchor
            str += "$";

            return str;
        }

关注点

作为一个懒惰的程序员,并且经常处理位字段,我经常需要同时更新几十个 NumberBox,这需要几十行代码来单独访问每个 NumberBox。相反,当我看到上面右侧面板中的所有 NumberBox 控件时,我发现遍历面板中的所有控件并逐个更新它们更方便。尽管在这里,我将 8 行代码减少到了 11 行,但我经常处理包含多达 100 个项目的控件列表。

对于这个演示,我遍历了控件列表,查找特定类型的控件。您也可以按名称遍历控件列表。或者,如果您为控件分配了唯一的 Tag,则可以查找唯一的 Tag。此外,所示的遍历仅为一层深度,您还可以构建代码进行深度遍历。任何容器控件都有一个 'Controls' 列表,您可以通过这种方式进行操作。

致谢

我绝不是第一个想到这种控件类的原创作者。过去我曾读过许多关于在此论坛上实现类似功能的文章。不幸的是,我丢失了它们的链接,因此无法直接注明出处。我只能站在巨人的肩膀上。

历史

  • 2019年5月7日 初始版本
© . All rights reserved.