Introduction
In this article, we'll learn step-by-step how to create a custom textbox control that only accepts numeric characters. The control will allow the client code to specify whether it should work as an integer or decimal textbox.
This custom control will be implemented using Visual Studio 2008. However, the same code can be written using previous versions of Visual Studio. The programming languages I'm going to use are: C# and JavaScript.
Prepare the Project
First of all, let's prepare the project and the files that we're going to use. Please follow these simple steps:
- Create a new project of type Class Library and name it MyCustomControls.
- Make sure that you add a reference to the System.Web library.
- By default, Visual Studio will add a file named 'Class1.cs'. Please delete this file and add two files with the following names: NumericTextBox.cs and NumericTextBoxExtention.cs. In addition, create a new folder with the name 'Resources' and add a JavaScript file with the name 'NumericTextBox.js'.
- In order to make the JavaScript embedded in the component, we need to change its
BuildAction
property to 'Embedded Resource'. Please refer to the figure below:
The C# Code
Why two C# files?
As you may have already noticed, we have two C# files. Both of them will be used to implement the same class;
NumericTextBox
. However, the keyword partial
will be added to the definition so the compiler will know that both classes represent one class in the end. The NumericTextBox.cs file will be used to implement the logic of our control, while the NumericTextBoxExtention.cs will contain any fields or properties.Please have a look at the code below to see how we're going to define the
NumericTextBox
class in the NumericTextBox.cs file. Code Snippet 2 shows the code in the NumericTextBoxExtention.cs file.Please note that the partial classes is a feature added to .NET version 2.0 and later versions. If you're using a 1.* version of .NET, you can alternatively place all your code in one class.
1: //
2: // Code Snippet 1
3: //
4:
5: using System;
6: using System.Web.UI;
7:
8: [assembly: WebResource("MyCustomControls.Resources.NumericTextBox.js", "text/javascript")]
9: namespace MyCustomControls
10: {
11: [ToolboxData(@"<{0}:NumericTextBox Text="""" runat=""server""></{0}:NumericTextBox>")]
12: public partial class NumericTextBox : System.Web.UI.WebControls.TextBox
13: {
14:
15: }
16: }
1: //
2: // Code Snippet 2
3: //
4:
5: namespace MyCustomControls
6: {
7: public partial class NumericTextBox : System.Web.UI.WebControls.TextBox
8: {
9:
10: }
11: }
Fields and properties
There are some attributes added in Code Snippet 1. Don't worry about them now, we'll talk about them later in this article. Now we want to see what properties we need to implement in the NumericTextBoxExtention.cs file.
Let's think of properties that we might need. First of all, we've said previously that our control will work for both integers and decimals. So it would be wise to add a property named
Type
. This property can be of type int
or string
. However, I think it's a better practice to add an enum
named TextBoxType
and let our property's type be of this enum.Additionally, we'll need a property to specify the number of integers allowed. And, in the case of the decimal textbox, we'll also need a property to specify the maximum number of fractions allowed after the decimal point. Furthermore, we'll add a property to specify whether this control should accept negative values or not. Please have a look at Code Snippet 3 to see how this will be implemented.
1: //
2: // Code Snippet 3
3: //
4:
5: public enum TextBoxType
6: {
7: Integer,
8: Decimal
9: }
10:
11: private int _NumberOfFraction = 0;
12: private int _NumberOfInteger = 0;
13: private bool _AllowNegative = false;
14: private TextBoxType _Type = TextBoxType.Integer;
15:
16: public int NumberOfFraction
17: {
18: get { return _NumberOfFraction; }
19: set { _NumberOfFraction = value; }
20: }
21:
22: public int NumberOfInteger
23: {
24: get { return _NumberOfInteger; }
25: set { _NumberOfInteger = value; }
26: }
27:
28: public bool AllowNegative
29: {
30: get { return _AllowNegative; }
31: set { _AllowNegative = value; }
32: }
33:
34: public TextBoxType Type
35: {
36: get { return _Type; }
37: set { _Type = value; }
38: }
Attributes
In order to let the compiler know that there's a resource that should be attached here, we need to add an attribute to the namespace, as you can see in Code Snippet 1. We've specified the path of the JavaScript file following the pattern: "Project Name.Folder Name.File Name".
In Visual Studio, drag and drop any ASP.NET control to your page. It will automatically create the necessary tags and attributes. In order to let our control have the same behavior, we've added an attribute to the class name. Please refer to Code Snippet 1.
{0}
will be replaced by a value that you specify in your web.config or in any config file. For ASP.NET controls, it's by default asp
.Please refer to Code Snippet 4 to see how you can specify this value in your web.config. So when dragging and dropping our control to a web page, it will look like Code Snippet 5.
1: <!--//
2: // Code Snippet 4
3: //-->
4:
5: <pages>
6: <controls>
7: <add tagPrefix="mcc" namespace="MyCustomControls" assembly="MyCustomControls"/>
8: .
9: .
10: .
1: <!--//
2: // Code Snippet 5
3: //-->
4:
5: <mcc:NumericTextBox Text="" runat="server"></mcc:NumericTextBox>
Override OnPreRender
We need to override
OnPreRender
to implement the way we want the control to be pre-rendered. In other words, we want to let the OnPreRender
method know that we want our control not only do what a normal textbox does, but also have other features.There are basically two things that we're going to do in the
OnPreRender
method. The first thing is to add code that will let the method add a reference to our JavaScript file. And, the second this is adding calls to JavaScript functions on certain events. Please refer to Code Snippet 6.1: //
2: // Code Snippet 6
3: //
4:
5: protected override void OnPreRender(EventArgs e)
6: {
7: base.OnPreRender(e);
8:
9: ClientScriptManager scriptManager = this.Page.ClientScript;
10: string resourceFilePath = "MyCustomControls.Resources.NumericTextBox.js";
11:
12: // This will register a Javascript block witht the name 'NumericTextBoxScript'
13: scriptManager.RegisterClientScriptInclude("NumericTextBoxScript",
14: scriptManager.GetWebResourceUrl(this.GetType(), resourceFilePath));
15:
16: if (this.Type == TextBoxType.Decimal)
17: this.Attributes.Add("onkeydown", string.Format("return CheckDecimal(this,'{0}','{1}', {2})", NumberOfInteger, NumberOfFraction, _AllowNegative.ToString().ToLower()));
18: else if (this.Type == TextBoxType.Integer)
19: this.Attributes.Add("onkeydown", string.Format("return CheckInteger({0})", _AllowNegative.ToString().ToLower()));
20:
21: this.Attributes.Add("onkeyup", string.Format("return CheckNegative(this)", _AllowNegative.ToString().ToLower()));
22: }
JavaScript Code
We're going to have three JavaScript functions. The first one,
CheckInteger
, will be called on the keydown event if the control is working as an integer textbox. The second, CheckDecimal
, will be called on the keydown event when the control is working as a decimal textbox. Finally, the third function, CheckNegative
, will be called on the keyup event no matter how the control is working.CheckInteger
will simply check whether the ASCII code of the character pressed is within the allowed list. It will also make sure that there's no more than one dash (-) in the textbox.The reason why this function is being called on the keydown event, not the keyup, is that the keydown event is raised prior to setting the value of the textbox, so I still have a chance to terminate this action. Therefore, if the function returns
false
on the keydown event, the character won't be added to the textbox.On the other hand, if the function returns
false
on the keyup event, nothing will change. However, the benefit of the keyup event is that it allows me to read the value of the textbox, since it's raised after the value has been set.1: //
2: // Code Snippet 7
3: //
4:
5: function CheckInteger(allowNegative) {
6: if ((event.keyCode >= 48 && event.keyCode <= 57 && event.shiftKey == false) || // 0-9 numbers
7: (event.keyCode >= 96 && event.keyCode <= 105 && event.shiftKey == false) || // 0-9 numbers (the numeric keys at the right of the keyboard)
8: (event.keyCode >= 37 && event.keyCode <= 40) || // Left, Up, Right and Down
9: event.keyCode == 8 || // backspaceASKII
10: event.keyCode == 9 || // tabASKII
11: event.keyCode == 16 || // shift
12: event.keyCode == 17 || // control
13: event.keyCode == 35 || // End
14: event.keyCode == 36 || // Home
15: event.keyCode == 46) // deleteASKII
16: return true;
17: else if (event.keyCode == 189 && allowNegative == true) { // dash (-)
18: if (sender.value.indexOf('-', 0) > -1)
19: return false;
20: else
21: return true;
22: }
23: else
24: return false;
25: }
CheckDecimal
is very similar to CheckInteger
. But they are different in certain things. CheckDecimal
will treat a numeric character in a different way than the other allowed keys. This is because when a number is pressed, we need to check if the number of integers and fractions is still within the limits.In addition, it will have a special treatment to the decimal point character. It shouldn't be the first character in the textbox, and it shouldn't appear more than once.
1: //
2: // Code Snippet 8
3: //
4:
5: function CheckDecimal(sender, numberOfInteger, numberOfFrac, allowNegative) {
6: var valueArr;
7:
8: if ((event.keyCode >= 37 && event.keyCode <= 40) || // Left, Up, Right and Down
9: event.keyCode == 8 || // backspaceASKII
10: event.keyCode == 9 || // tabASKII
11: event.keyCode == 16 || // shift
12: event.keyCode == 17 || // control
13: event.keyCode == 35 || // End
14: event.keyCode == 36 || // Home
15: event.keyCode == 46) // deleteASKII
16: return true;
17: else if (event.keyCode == 189 && allowNegative == true) { // dash (-)
18: if (sender.value.indexOf('-', 0) > -1)
19: return false;
20: else
21: return true;
22: }
23:
24: valueArr = sender.value.split('.');
25:
26: if (event.keyCode == 190) { // decimal point (.)
27: if (valueArr[0] != null && valueArr[1] == null)
28: return true;
29: else
30: return false;
31: }
32:
33: if ((event.keyCode >= 48 && event.keyCode <= 57 && event.shiftKey == false) || // 0-9 numbers
34: (event.keyCode >= 96 && event.keyCode <= 105 && event.shiftKey == false)) { // 0-9 numbers (the numeric keys at the right of the keyboard)
35: if (valueArr[1] == null) {
36: if (valueArr[0].indexOf('-', 0) > -1)
37: numberOfInteger++;
38:
39: if (valueArr[0].length <= numberOfInteger)
40: return true;
41: }
42: else {
43: if (valueArr[1].length <= numberOfFrac)
44: return true;
45: }
46: }
47:
48: return false;
49: }
When the key is up, the
CheckNegative
function will make sure that the dash/minus character is added to the left.1: //
2: // Code Snippet 9
3: //
4:
5: function CheckNegative(sender) {
6: if (event.keyCode == 189) { // dash (-)
7: if (sender.value.indexOf('-', 0) > 0)
8: sender.value = sender.value.replace('-', '');
9: }
10: }
No comments:
Post a Comment