Introduction
ASP.NET provides a mechanism to validate user input. That is, before a user submit his/her page, there are several kinds of validators that validate the input. However, in WinForms, we can't get those excellent components. When we validate a text in a control, for example, there is a TextBox
named "txtPassword
", and it requires users to input a password text beginning with a letter and having 3-8 length numbers or letters, we must register txtPassword
's Validating
event, and the event method should like:
private void txtPassword_Validating(object sender, CancelEventArgs e)
{
if (string.IsNullOrEmpty(txtPassword.Text))
{
errorProvider1.SetError(txtPassword, "Password required!");
}
else if (!Regex.IsMatch(txtPassword.Text, @"[A-Za-z][A-Za-z0-9]{2,7}"))
{
errorProvider1.SetError(txtPassword, "Password invalid!");
}
else
{
errorProvider1.SetError(txtPassword, null);
}
}
In the Submit button, the code should be like:
private void btnOK_Click(object sender, System.EventArgs e)
{
foreach (Control control in this.Controls)
{
control.Focus();
if (!Validate())
{
DialogResult = DialogResult.None;
return;
}
}
}
It's really a dull assignment, and if there is a lot of controls in your form, it makes maintenance difficult. My goal is to validate controls without any code! With Validator
, you drop it to a form and enter the validation rules for a TextBox
or a ComboBox
just like using the ToolTip
component.
Background
I got the afflatus from the following article but implement a different approach. I suggest you read it:
Using the code
First, create a new Windows Forms application, and follow these steps to use the Validator
component:
- Add assembly
Open the "Toolbox" window, right click in the "General" tab, and click "Choose items...".
If you have strong named Validator assembly and has added it to the GAC, you can choose it in the ".NET Framework components" list. Or else, in the example, click the "Browse" button and locate your Validator.dll assembly.
Now, you can find it in "Toolbox".
- Add and configure the component
Drag the Validator
which has been added by the first step, and drop it on the form. Its property window should be like:
The important properties for this component are:
Form
- specify a form where validated controls lay onto. Validator
will register its FormClosing
event, validates controls, and decides whether to close the windows.
Mode
- this enum
value can been one, or combined by FocusChange
and Submit
. FocusChange
means when validating a control fails, the focus stops on that control or is changed to the next tab index control; Submit
means that validate controls while the form is closing, and prevent the form from closing when the validation fails.
BlinkRate
, BlinkStyle
, Icon
, RightToLeft
: same as ErrorProvider
properties, please use MSDN for help.
- Configure the TextBoxes and their properties
Now we will assume that there are three TextBox
es and one Submit button on the form.
Name | Text | Properties | Function |
---|
txtName | | | User name, required input. |
txtPassword | | PasswordChar = "*" | Input password, begins with an alphabet and has 3-8 numbers or alphabets. |
txtRePassword | | PasswordChar = "*" | Re-input password, same as password. |
txtSubmit | Submit | DialogResult = OK | Submit and close form. |
Configure validation info for the controls. Now, configure txtRePassword
for the demo: show the txtRePassword
property window. You will surprisingly find an extra category, "Validation
", there:
Type
: dropdown the "Type on validator1" item and select the Required
and Compare
items.
RequiredMessage
: input - "Please input re-password.".
ComparedControl
: dropdown and select "txtPassword
".
CompareOperator
: dropdown and select "Equal
".
CompareMessage
: input "Re-Password not same as password".
The extended properties should be like the following table (omit message):
Name | Validation |
---|
txtName | Type =Required |
txtPassword | Type = Required|RegularExpression
RegularExpression = ^[A-Za-Z][A-Za-z0-9]{2,7}$
RegularExpressionOptions = None |
txtRePassword | Type = Required|Compare
ComparedControl = txtPassword
CompareOperator = Equal |
- Test your form
Congratulations! You got to the point of using the Validator
. Run your form, and when you enter an invalid value in the TextBox
, it will show an error message. You click the "Submit" button, it will not close the window while there are still errors. For a full demo, I have got a "register" example for you, please analyse it yourself. If you have any problems, feel free to contact me.
Points of Interest
In this section, I want to say more about advanced components programming. It will be a guide to people who are interested in my Validator
component and will show the key points of the Validator
. Here, I will assume you that have basic components programming skills, like creating a component class, applying DefaultValue
, DefaultEvent
, and Browsable
attributes, and so on.
- Extend property
ErrorProvider
, ToolTip
, and Validator
- these components provide a mechanism which extends controls' (or components') properties, in the property window. You will see text like "Type on validator1", which means Type
property is an extended property and is provided by the validator1
control (or component). To have this amazing feature:
- The component must implement the
IExtenderProvider
interface.
public partial class Validator : Component, IExtenderProvider
IExtenderProvider
has an abstract method that must be implements (from MSDN):
CanExtend
: Returns whether or not the provider can provide an Extender for the given object in the specified category.
In Validator
, we validate controls which allow user inputs, like ComboBox
(simple, dropdown), TextBox
, RichTextBox
, thus, the Validator
's CanExtend
method is:
public bool CanExtend(object extendee)
{
if (extendee is TextBoxBase || extendee is ComboBox)
{
return true;
}
else
{
return false;
}
}
- Apply the
ProvideProperty
attribute and the correlated Get/Set method.
ProviderProperty
attribute shouldn't run solely, it must have the Get/Set pair methods in the component class. In Validator
, I define an extended property for the control.
[ProvideProperty("Type", typeof(Control))
......
public partial class Validator : Component, IExtenderProvider
Also, has Get/Set methods:
public partial class Validator : Component, IExtenderProvider
{
DefaultValue(Itboy.Components.ValidationType.None)]
... ...
public ValidationType GetType(Control control)
{
... ...
}
public void SetType(Control control, ValidationType type)
{
... ...
}
}
- Editor for the property window
When you edit the ForeColor
property for a TextBox
in the property window, it drops down a Color Editor dialog.
When you edit the Lines property for a TextBox
in the property window, it shows a String Collection Editor dialog.
Actually, a property window only provides a simple text input editor as default. How do Color and Lines properties do this? Using the Reflector v4.2 to disassemble the System.Drawing.Color struct
in the System.Drawing.dll assembly, we find this struct
that applies the Editor
attribute.
[Editor("System.Drawing.Design.ColorEditor, ..."]
Disassemble the System.Windows.Form.TextBoxBase
(TextBoxBase
derives from TextBox
) class in the System.Windows.Form.dll assembly, and we will find that the Lines
property applies the Editor
attribute.
[Editor("System.Windows.Forms.Design.StringArrayEditor, ..."]
The Editor
attribute can be applied to either a class (struct) or a property. If class A
applies the Editor
attrubute, any other class can define properties of class A. Those properties will always be edited by the specified editor in the property window.
Only those properties which apply the Editor
attrubute will be edited by the specified editor; although other property types are same, they will be edited by the default simple text box.
In the Validator project, I wrote a FlagEditor
class, and it can edit the Flag enum
type. Like the RegexOptions
enum
, you can choose the bit value set using | (logic OR), or click the "None
" button to set enum
value to 0.
To implement the Editor
attribute, the class must derive from UITypeEditor
:
public sealed partial class FlagsEditor : UITypeEditor
and override the EditValue
and GetEditStyle
methods. For details, you can refer to MSDN.
My FlagsEditor
class:
public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider, object value)
{
.....
if (!value.GetType().IsEnum)
{
return value;
}
object[] attributes =
value.GetType().GetCustomAttributes(typeof(FlagsAttribute),
false);
if ((attributes == null)
|| (attributes.Length == 0))
{
return value;
}
.....
if (service != null)
{
FlagsEditorControl control = new FlagsEditorControl(value);
service.DropDownControl(control);
Type type = value.GetType();
object newValue = Activator.CreateInstance(type);
FieldInfo field = type.GetFields(BindingFlags.Public |
BindingFlags.Instance)[0];
field.SetValue(newValue, control.EditValue);
}
......
}
First, it asserts that we can only edit the enum
type with Flags
attribute. Secondly, it drops down a user control which has a "None
" button to set the enum
value to 0, and has several check list items each of which represents one enumeration member except "None
".
- Component Designer
Have you noticed that, if you set the focus on a ListView
control at design time, there appears a triangle button on it. When you click it, it will show a context menu.
Besides, when you drop a ErrorProvider
to a Form
, it will automatically generate a line like:
this.errorProvider1.ContainerControl = this;
How does C# do that? In Validator
, I apply the Design
attribute:
[Designer(typeof(Validator.ValidatorDesigner), typeof(IDesigner))]
ValidatorDesign
derives from ComponentDesign
which provides an approach to above issues. ComponentDesign
has a virtual property ActionLists
, which returns a menu like the ListView
control, and has a virtual
method InitializeNewComponent
telling the IDE how to generate code.
History
- April 11th, 2006 - First version.
Thanks to
- Michael Weinhardt, for his article in MSDN.
- Bo Shen, who checks my article and corrects my English.