Designing Inherited Controls for Windows Forms


Introduction

If you have worked on VB6 based thick client applications, one common problem you as a developer would have encountered is the way controls work. There were a handful of issues in terms of customization of controls. .Net answers them through use of customizing existing controls using Inherited controls or creating a new control by inheriting User Control. This article uses Windows Form's TextBox control to explain how to design your controls.

There is difference between Inherited Controls and User Controls in practical implementation even though conceptually both can be grouped under Custom Controls category. Inherited controls are those existing controls that user would inherit to add custom functionalities. E.g. TextBox, DataGrid; Usually these are used for common validations, GUI features etc. User Controls are controls that user creates using either one or more controls, add some common application logic. This inherits "System.Windows.Forms.UserControl"; so one can add a Combo Box, DataGrid, ImageList etc. Make the modifiers public, protected; add some common application logic like on selection of Combo value the DataGrid is revalidated.

Why?

Consider a very simple scenario in Windows Forms. I want to validate a user's entry to a TexBox control i.e either numeric, text, URL etc. One way to do that would be just add keypress event of textbox as follows:

public class frmTestControls : System.Windows.Forms.Form
{
private System.Windows.Forms.TextBox txtLogin;
private System.ComponentModel.IContainer components;
public frmTestControls()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.txtLogin = new System.Windows.Forms.TextBox();
this.KeyPress += new KeyPressEventHandler(OnKeyPress);
this.SuspendLayout();
//Some more code goes here …
}
private void OnKeyPress(object sender,KeyPressEventArgs e)
{
//Some code goes here …
}
}

Let us consider that you did the above for a textbox control that should allow only numerical entries and had a specific format "0.00". This was implemented throughout the application that had around 30-40 user input boxes of type numeric. One fine day you want the numeric format to be "0.000". Now to get this small change done we may have to once again do a code change through out the application. Many might argue that I would use application config files to get these values. I agree, but consider a situation that had events like MouseDown, Leave, Enter, LostFocus etc that may have been used for each of these textboxes for whatever GUI features or common validations as applicable. A better approach to do this would be to use the Inherited Controls judiciously.

Sample Implementation

Objective: Design for common TextBox control for the application to manage changes with ease.

Step 1:

This textbox should be flexible to validate for all type of user entries. First design your constructors for controls. In this case nothing much to do in terms of overloading, hence we go with the default constructor. Create an Enum for validation types to implement the same. I use Enum because today I have only Numeric and Text as the types but in future I could add URL, Social Security Number, some specifc patterns to identify inventory bins like '000-XXX' etc. Then decide on Events based on the type of features and validations that the textbox must provide. In the example I have selected the Leave and Enter events to add some UI features. KeyPress and LostFocus is an event that I require for validating most events.

namespace Orbit-e.Controls
{
public enum ValidationType
{
Text=0,
Numeric,
URL,
SSN,
InventoryBin_Pattern
}
public class in4CommonTextBox : System.Windows.Forms.TextBox
{
public in4CommonTextBox():base()
{
InitializeComponent();
this.AutoSize =false;
this.Width =160;
this.Height =20;
this.ForeColor = Color.Blue;
this.BackColor = Color.White;
this.BorderStyle = BorderStyle.Fixed3D;
//Set for all the events and write the corresponding events
this.KeyPress += new KeyPressEventHandler(OnKeyPress);
this.Leave +=new EventHandler(onLeave);
this.Enter +=new EventHandler(onEntering);
this.MouseDown += new MouseEventHandler(onMouseDown);
this.LostFocus +=new EventHandler(OnLostFocus);
}
}
}

Step 2:

Decide on how to handle the GUI features like Height, Width, color and style. If personalization features like themes are to be provided then usually one way to do this is to create a user configuration file for storing theme schemes that include BackColor, ForeColor. A common technique to figure out the default color schemes would help for designing other controls as well. The height and Width are very important aspects since this helps maintain uniformity in the UI looks. Also add accessor to set the type of validation and make sure that there is always a default value assigned.

private int intValidType=(int) ValidationType.Text
public ValidationType ValidateFor
{
set
{
intValidType = (
int) value;
}
}

Step 3:

By now we know what all validations we have to do. Decide upon which event handlers to use for what. Use events like MouseLeave, Enter, LostFocus, MouseHover to do UI Jazz.

protected
void OnLostFocus(object sender,EventArgs e)
{
this.BackColor =Color.White;
this.ForeColor =Color.Blue;
}
protected void onEntering(object sender,EventArgs e)
{
this.SelectAll();
this.BackColor = Color.Blue ;
this.ForeColor = Color.White;
}
protected void onMouseDown(object sender,MouseEventArgs e)
{
this.SelectAll();
}

As a sample I have used the KeyPress event to validate for numeric value. The following code can be optimized further as required.

private int intDecimalPlace=2;
protected void OnKeyPress(object sender,KeyPressEventArgs e)
{
try
{
switch (intValidType)
{
case (int) ValidationType.Numeric:
{
#region "Numeric Validation Rule"
int intActDecPlace= this.Text.IndexOf(".");
//Check & Reset boolean if the decimal place does not //exist
if(intActDecPlace==-1)
{
blndecimalClick=
false;
}
//If Keypressed is of type numeric or "."
if(Char.IsDigit(e.KeyChar))
{
#region "Decimal place check"
int intTotDecPlace= this.Text.IndexOf(".");
//Decimalplace validation
if(intTotDecPlace > 0 && ((this.Text.Length - intTotDecPlace) > intDecimalPlace))
{
e.Handled =
true;
return;
}
#endregion
e.Handled =false;
}
else if(e.KeyChar.ToString()==".")
{
#region "Decimal validation"
if(blndecimalClick!=true)
{
blndecimalClick=
true;
e.Handled =
false;
}
else{e.Handled =true;}
#endregion
}
else
{
e.Handled =
true;
}
#endregion
break
;
}
case (int) ValidationType.Text:
{
//Nothing here..
break;
}
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message,"Validation",MessageBoxButtons.OK,MessageBoxIcon.Error);
}
}

Also I have used the Leave event to do some personalization. Here I am using the local Windows settings format to show numeric value.

protected void onLeave(object sender,EventArgs e)
{
if(this.intValidType ==(int) ValidationType.Numeric)
{
CultureInfo ci = CultureInfo.CreateSpecificCulture("en-US");
NumberFormatInfo ni = ci.NumberFormat ;
string strnumformat= d.ToString("c",ni) ;
this.Text = return strnumformat.Remove(0 ,1);
}
this.BackColor =bgColor;
this.ForeColor =fColor;
}

Step 4

We have seen how to create the inherited controls. You may ask what great have we done, we used the OOPS concepts to inherit and extend a controls behavior.I would say that's not the end; we can keep extending this control by adding our own events.

For E.g There is a high possibility that once user entered value was validated for numeric, you would want to receive an event notification on leaving the control. For this I would add a simple event that is fired within "Leave" event.

namespace Orbit-e.Controls
{
public delegate void DuringLostFocusArgs(int ValidationType);
public enum ValidationType
{
Text=0,
Numeric,
URL,
SSN,
InventoryBin_Pattern
}
public class in4CommonTextBox : System.Windows.Forms.TextBox
{
public event DuringLostFocusArgs DuringLostFocusHandler;
//Code continues…
protected void onLeave(object sender,EventArgs e)
{
if(this.intValidType ==(int) ValidationType.Numeric)
{
CultureInfo ci = CultureInfo.CreateSpecificCulture("en-US");
NumberFormatInfo ni = ci.NumberFormat ;
DuringLostFocusArgs(intValidType);
string strnumformat= d.ToString("c",ni);
this.Text = return strnumformat.Remove(0 ,1);
}
this.BackColor =bgColor;
this.ForeColor =fColor;
}
}

While
using this control one can un/subscribe to this event as follows:

in4CommonTextBox txtLogin =
new in4CommonTextBox();
this.txtLogin.DuringLostFocusHandler += new DuringLostFocusArgs(RunTest);
private
void RunTest(int ValidationValue)
{
MessageBox.Show(intI.ToString());

 
Conclusion

Inherited controls is a very strong implementation concept especially if you are working on a Windows application.They help maintain your applications better. Design controls that are flexible and customizable with a thought on various Datatypes, Patterns that are specific to your application. Be careful not to over-do the intense operations in controls, rather move them for form level validations since there will be specific validations at this level.

Happy Programming!