This NumberBox component code provides an easy method for number entry, display, range limiting and keystroke validation using SNFS definitions with a new binary definition added.
Introduction
This NumberBox
control provides a convenient single control class to enter and display numbers in multiple formats. This class validates key strokes depending on the SNFS specified. This class also does a range limit check when the 'Leave
' event fires. It works with SNFS types 'E
', 'F
', 'D
', 'N
' and 'X
'. A new type of 'B
' for binary is introduced. Values can be written in one format by the user and retrieved by the program in another format.
After the first compile, the NumberBox
control will show up in your toolbox and can be dragged into your GUI design just as any other control.
Background
I program a lot of GUIs designed to connect directly to chips. These GUIs require numbers to be entered or displayed in multiple formats from doubles to hex and binaries all of various sizes and widths. Often, numbers will be read from the target in binary but I wanted to display them in decimal to the user. I got tired of writing these conversions over and over again so the lazy programmer in me decided to create one class that does it all.
Using the Code
Demo Window
Below is the demo window for this class. The left panel is where you enter numbers with keystroke validation depending on SNFS specified. When the control leave
event fires, the value will be written back to the left panel with the display matching the SNFS and right panel controls will be updated with the new value range limited and presented in their respective SNFS format.
The main code behind this window looks like this:
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;
}
}
}
}
Being a lazy programmer, I like to write only one event handler for multiple controls of the same type. The numberBox_Leave
event handler is connected to all the NumberBox
controls in the left panel. When the event fires, the event handler sweeps through all the controls in the right hand panel and if the control is a NumberBox
, it then updates the control based on its SNFS type.
NumberBox
The NumberBox
class is derived from TextBox
. The text in the Text
field of the TextBox
is the real storage location of the value. There no other internal or private locations. What you see is what you get.
The following properties have been added:
ValueAsByte
ValueAsDouble
ValueAsFloat
ValueAsInt16
ValueAsInt32
ValueAsInt64
ValueAsSByte
ValueAsUInt16
ValueAsUInt32
ValueAsUInt64
MaxValue
MinValue
Prefix
Suffix
Snfs
The ValueAs
... properties get/set the Text
string using the proper casting.
The Min
/Max
properties get/set the range limits applied to the numbers.
The Snfs
property is the Standard Numeric Format String defined in Microsoft documentation. I've added a new format 'B
' for binary numbers.
NumberBox Code Flow
After a NumberBox
control is instantiated and when an SNFS is set, a regular expression is created for the specific SNFS. This regular expression '_pattern
' is used to valid key strokes validation during the '_KeyDown
' and '_KeyPress
' events within the control.
All the magic happens when the user leaves the control:
- The control reads the text from the
NumberBox
- Removes any prefixes or suffixes
- Converts it to a double based on the SNFS format
- Range limits to the
Min
/Max
values - Writes it back to the control in the correct format.
As the main demo program shows, a 'Leave
' event can be fired to perform any other actions required. In this demo, the 'Leave
' event updates the 8 NumberBox
es in the right panel. The 2 leave
events are called sequentially with the control firing first and the main program event firing last (standard C# event chaining).
Regular Expression Builder
The key section of the control is building the regular expression to valid the key strokes as they are typed in the control. This code is shown below. One key point is the 'B
' lines for adding binary to the SNFS family.
private string BuildRegularExpression()
{
if (_snfsEmpty) return string.Empty;
var str = "^";
var len = LengthParameter(_snfs);
if (!_prefixEmpty)
str = Prefix.Aggregate(str, (current, c) => current + ("[" + c + "]"));
if (_snfs.StartsWith("D", StringComparison.OrdinalIgnoreCase))
{
str += "-?";
if (len == 0) len = 4;
for (var i = 0; i < len; ++i) str += @"\d?";
}
if (_snfs.StartsWith("E", StringComparison.OrdinalIgnoreCase))
{
str += @"-?\d*[.,]?";
if (len == 0) len = 6;
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;
for (var i = 0; i < len; ++i) str += @"\d?";
}
if (_snfs.StartsWith("N", StringComparison.OrdinalIgnoreCase))
{
str += @"-?\d*[., ]?";
if (len == 0) len = 2;
for (var i = 0; i < len; ++i) str += @"\d?";
}
if (_snfs.StartsWith("X", StringComparison.OrdinalIgnoreCase))
{
if (len == 0) len = 2;
for (var i = 0; i < len; ++i) str += @"[0-9A-Fa-f]?";
}
if (_snfs.StartsWith("B", StringComparison.OrdinalIgnoreCase))
{
if (len == 0) len = 1;
for (var i = 0; i < len; ++i) str += @"[0-1]?";
}
if (!_suffixEmpty)
str = _suffix.Aggregate(str, (current, c) => current + ("[" + c + "]"));
str += "$";
return str;
}
Points of Interest
Being a lazy programmer and working with bit fields, I will often have dozens of NumberBox
es to update at once which would require dozens of lines of code to access each NumberBox
separately. Instead, when I see all the NumberBox
es in one control like the right panel above, I find it easier just to sweep through all the controls in the panel and update them as I go. Even though here, I reduced 8 lines to code down to 11 lines, I often work with controls lists with up to 100 items.
For this demo, I swept the control list looking for a control of a specific type. You can also sweep the control list for a specific name. Or if you assign unique Tags to the controls, you can look for the unique tag. Also the shown sweep is only one layer deep, you can also build code to sweep in depth. Any container control has a 'Controls
' list you can work with this way.
Credits
I am far from the original person to come up with this type of control class. There were many articles I've read in the past that implement similar functionality in this forum. Unfortunately, I have lost their links so I cannot give them credit directly. I can only stand on their shoulders.
History
- 7th May, 2019 Initial version