|
|||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIt seems common sense that when building a Numeric Edit (Text) Box, one should validate each character by the time it is typed and reject whatever is not a digit. Well, maybe accept a minus for the first char. Maybe just one dot as the decimal point? What about commas as separators for thousands? In this article, I'm going to present another approach: just let the monkey type whatever and do the validation in the end. This way, not only the control would accept decimals, thousands separators, or scientific exponent notation, but will also allow and validate pasted values in the box. In the end, I'll show you an interesting and unexpected way to extend the usage of this control. And I hope you will get enough knowledge to solve the proposed exercises by yourself. Create the projectFirst, create a blank solution. Give it a name like NumBox. Add a new project for hosting and testing the new control. Select the template Windows Application and give it a name like TestNumBox. Rename the form from Now, add the project to represent our new control. Select the template Windows Control Library and give it the name NumEditBox. This project will be special - we want to reuse it in various other projects. This is why we should pay attention to place it in a suitable namespace. Right-click on the project node NumEditBox in Solution Explorer, and then from the pop up menu, select Properties.
Change the 'Default Namespace' to - let's say - To make sure that we work in this namespace, copy it, then click the NumEditBox.cs node in Solution Explorer, press [F7] to go to code, and paste it over the namespace name - whatever was there. Also being there, remember to change the inheriting control Quite annoying that, when coming back to the NumEditBox.cs item from Solution Explorer, Visual Studio presents us an useless designer page. Fortunately, we can correct this behavior by adding the attribute At this moment, the file should start like that: using System.ComponentModel;
using System.Windows.Forms;
namespace TH.WinControls
{
/// <summary>
/// Summary description for NumEditBox.
/// </summary>
[DesignerCategory ("Code")]
public class NumEditBox : TextBox
{
//...
Let aside the comments for now: let's see what we can do from this control, and we'll describe it in the end. Put it on the ToolboxBefore adding features, let's put it on the toolbox and on the main form of the test program. However, before doing that, let's prepare a bitmap to represent the new control on the toolbox. Add a new item to the project that will be a bitmap. Name this bitmap with the same name as the control - NumEditBox.bmp.
It does not mater much if you've selected from Local Project Items or Resources, but the next step is essential to save you hours of frustrations and unsuccessful trial and error, until you will decide to delete this file and start again. So, as soon as you press the Open button, a new tab is created and you are invited to edit the bitmap. However, before doing any change to the content or size of this blank bitmap, go back (double-click any other item in the project) and click again the NumEditBox.bmp item. Look in the 'Properties' box:
Change the Build Action from Content to Embedded Resource. Now, you may go and edit the content of the bitmap! Click in the edit area. Look at the 'Properties' box:
Select 16 Color palette, then set Now, draw a nice picture, something like: To put the new control on the toolbox, go in the designer of Should you change the appearance of the bitmap, you have to delete and add again the control in Toolbox; but only then! Experiment a whilePut some Double-click the edit zone of the private void numEditBox1_Validating(object sender,
System.ComponentModel.CancelEventArgs e) {
MessageBox.Show("Validating");
}
This way, we find out that it fires when exiting We can access the text of the box like: private void numEditBox1_Validating(object sender,
System.ComponentModel.CancelEventArgs e) {
string s = numEditBox1.Text;
But better, to make it more general - for example, to be able to copy it or just to use it on the other box, do: private void numEditBox1_Validating(object sender,
System.ComponentModel.CancelEventArgs e) {
string s = ((NumEditBox)sender).Text;
To be able to do that, don't forget to add the line: using TH.WinControls;
Else the compiler would not know who Now consider the code: private void numEditBox1_Validating(object sender,
System.ComponentModel.CancelEventArgs e) {
string s = ((NumEditBox)sender).Text;
int l = s.Length;
while (s.Length>0) {
try {
int v = int.Parse(s,
System.Globalization.NumberStyles.Number |
System.Globalization.NumberStyles.AllowExponent);
break;
} catch {
s = s.Substring(0,s.Length-1);
}
if (s.Length!=l) {
// MessageBox.Show("Validating: Value= "+v.ToString());
((NumEditBox)sender).SelectionStart=s.Length;
((NumEditBox)sender).SelectionLength=l-s.Length;
e.Cancel=true;
}
}
}
We try to parse the input value reducing char by char from the end of the string You may find the commented line useful when experimenting. What you may find with this experiment:
Add some propertiesBefore integrating this code in the control, let's consider some new properties. Firstly, we may want to validate integers or floats by choice. For that, we need an //...
} // class NumEditBox
public enum ValidateType {
Integer,
Float
}
}
Now, use this type to define what to validate: private ValidateType _ToValidate;
[Category("Appearance"),
DefaultValue(ValidateType.Integer)]
public ValidateType ToValidate {
get { return _ToValidate; }
set { _ToValidate = value; }
}
The new private float _Value;
[Category("Appearance"),
DefaultValue(0) ,
Description("Convert it to integer when necesary.")]
public float Value {
get { return _Value; }
set {
_Value = value;
if (ToValidate==ValidateType.Integer)
Text = System.Convert.ToInt32(_Value).ToString();
else
Text = _Value.ToString();
}
}
Note here that whatever is the value of the Not a very good idea!With these new properties, we can modify the test handler like this: ((NumEditBox)sender).Value =
(((NumEditBox)sender).ToValidate==ValidateType.Integer) ?
int.Parse(s, System.Globalization.NumberStyles.Number |
System.Globalization.NumberStyles.AllowExponent) :
float.Parse(s, System.Globalization.NumberStyles.Number |
System.Globalization.NumberStyles.AllowExponent);
break;
Change the public float Value {
get { return _Value; }
set { _Value = value; }
}
IntegrateWe have a pretty good idea now about how the validate should work. It is time for moving our code inside the control - for integrating. To begin with, let's have a gimp to the Windows Form Designer generated code region of MainForm.cs, click on the small [+] icon near the caption of this region, and find the related code: this.numEditBox1.Validating += new
System.ComponentModel.CancelEventHandler(this.numEditBox1_Validating);
Move this code in the constructor of the using System.Globalization;
//...
public NumEditBox():base() {
base.Validating += new
System.ComponentModel.CancelEventHandler(this.numValidating);
}
#region events
private void numValidating(object sender,
System.ComponentModel.CancelEventArgs e) {
string s = Text;
int l = s.Length;
NumberStyles NumStyles = NumberStyles.Number | NumberStyles.AllowExponent;
while (s.Length>0) {
try {
_Value = (ToValidate==ValidateType.Integer) ?
int.Parse(s, NumStyles) : float.Parse(s, NumStyles);
break;
} catch {
s = s.Substring(0,s.Length-1);
}
if (s.Length!=l) {
SelectionStart=s.Length;
SelectionLength=l-s.Length;
e.Cancel=true;
}
}
}
#endregion
Disable the corresponding code from Note that in order to reduce both the code and the number of private NumberStyles _NumStyles= NumberStyles.Number |
NumberStyles.AllowExponent;
[Category("Appearance"),
DefaultValue(NumberStyles.Number | NumberStyles.AllowExponent),
Description("Customize the validation number style.")]
public NumberStyles NumStyles {
get { return _NumStyles; }
set { _NumStyles = value; }
}
New Validating EventThe only problem left is that our private void numEditBox2_Validating(object sender,
System.ComponentModel.CancelEventArgs e) {
e.Cancel = ((NumEditBox)sender).Text!="Enter Int";
}
(Not) surprisingly it works very well: try to exit the box - it works; add or delete some chars from this phrase - you may not exit the box anymore and the entire text gets highlighted; delete it and enter a valid number - you may exit the box - it validates internally! However - consider a more sophisticated task. For example, if the user has typed private void numEditBox2_Validating(object sender,
System.ComponentModel.CancelEventArgs e) {
if (((NumEditBox)sender).Text=="Pi") {
((NumEditBox)sender).Value = (float)System.Math.PI; return;
}
if (((NumEditBox)sender).Text=="e" ) {
((NumEditBox)sender).Value = (float)System.Math.E ; return;
}
if (((NumEditBox)sender).Text=="C" ) {
((NumEditBox)sender).Value = (float)((1.0+System.Math.Sqrt(5.0))/2.0);
return;
}
e.Cancel = ((NumEditBox)sender).Text!="Enter Int";
}
To make it work, we have to reintroduce the #region events
public new event CancelEventHandler Validating;
private void numValidating(object sender,
System.ComponentModel.CancelEventArgs e) {
if (Validating!=null) {
Validating(sender,e);
if (!e.Cancel) return;
e.Cancel=false;
}
string s = Text;
//...
The If the internal handler senses some code on the new added event, this code is executed. Then, if the external validation was successful in this code, the internal validation stops (returns); else the ConclusionWe have developed from scratch a new control. I have done this as I was writing the article, without knowing in the beginning all details of the implementation, and if I was able to, you can too! This Extend the exerciseValidating constants could also be embedded in the control. Inherit from a combo box and add a collection property to store key-value items for constants to validate. Since the validation was done after typing the full value, this technique is suitable for building a similar
|
||||||||||||||||||||||||||||||||||||||||