|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionFor some time, I have been trying to eliminate some of the menial tasks that we as developers are continually performing. So, I set about writing a control library that would contain most of the standard For my first article, I will show you all how I went about creating my BackgroundWe often find ourselves, well I do, having a FormView or DetailsView auto-generate fields from a data source, then we go and apply all our validation to the controls on the page. Well, this is an introductory article to replace those controls within a page with controls that automatically have the validation setup as soon as they hit the Designer canvas. Further to this introduction, I am working on a custom framework to have an auto-generated This control has full design-time support through both the Using the codeI will start by introducing the Layout structure of the project, then we will walk through some areas of the code. The Solution tree looks like this:
As you can see, I have split the code up into the click2install.design and click2install.controls, whilst click2install.resource contains the embedded image resource for the control. Note: The test website has been left out of the download above. OK, let's dive into the code and see how it all works. To start with, I chose to extend Basically, what I have done is:
There you have it, three hours of coding reduced to five lines. So, let's take a look at each step, then we can move onto the design-time support that has been added through the use of Smart Tags. Although the source solution is commented, the listings here have no comments for brevity. Step 1: Create a class that extends namespace click2install.controls
{
[
ToolboxData("<{0}:RequiredTextBox runat="server"></{0}:RequiredTextBox>"),
Designer(typeof(RequiredTextBoxDesigner)),
]
public class RequiredTextBox : TextBox
{
#region constants
private const string DEFAULT_VALIDATOR_TEXT = " *";
#endregion
public RequiredTextBox()
{
ValidatorMessage = this.ID + " is a required field";
ValidatorDisplayType = ValidatorDisplay.Dynamic;
ValidatorEnableClientScript = true;
ValidatorFocusOnError = true;
}
A couple of things to note here straight away is the This is the excerpt of the relevant code from AssemblyInfo.cs to allow custom tag-prefixing: //
// assembly tag prefix
//
[assembly: TagPrefix("click2install.controls", "c2i")]
Okay, we will merge steps 2 and 3 for brevity, and list only the relevant code, designer attributes, and instance variables: #region Required Field Validator properties
[
Browsable(false),
]
private RequiredValidator m_RequiredFieldValidator = null;
private private RequiredValidator RequiredTextBoxValidator
{
get
{
if (m_RequiredFieldValidator == null) {
m_RequiredFieldValidator = new RequiredValidator(this);
}
return m_RequiredFieldValidator;
}
set { m_RequiredFieldValidator = value; }
}
internal bool IsDesignMode
{
get { return DesignMode; }
}
private ErrorProviderType m_ErrorProvider =
ErrorProviderType.StillIcon;
[
[
Description("The type of visual alert that
will be shown when validation fails"),
Category("Validator"),
DefaultValue(ErrorProviderType.StillIcon),
]
public ErrorProviderType ErrorProvider
{
get { return m_ErrorProvider; }
set
{
if (value == ErrorProviderType.Text)
{
RequiredTextBoxValidator.Text =
DEFAULT_VALIDATOR_TEXT;
}
m_ErrorProvider = value;
}
}
[
[
Description("The Validators Message when validation fails"),
Category("Validator"),
]
public string ValidatorMessage
{
get { return RequiredTextBoxValidator.ErrorMessage; }
set { RequiredTextBoxValidator.ErrorMessage = value; }
}
[<value>The type of the validator display.</value>[
Description("The Validators Display"),
Category("Validator"),
]
public ValidatorDisplay ValidatorDisplayType
{
get { return RequiredTextBoxValidator.Display; }
set { RequiredTextBoxValidator.Display = value; }
}
[
Description("The Validators FocusOnError"),
Category("Validator"),
]
public bool ValidatorFocusOnError
{
get { return RequiredTextBoxValidator.SetFocusOnError; }
set { RequiredTextBoxValidator.SetFocusOnError = value; }
}
[
Description("The Validators EnableClientScript"),
Category("Validator"),
]
public bool ValidatorEnableClientScript
{
get { return RequiredTextBoxValidator.EnableClientScript; }
set { RequiredTextBoxValidator.EnableClientScript = value; }
}
#endregion
As you can see, I have only exposed some of the validator properties to the user. It would be pointless to expose the I chose not to expose the Text property of the validator, as I seldom use this now as I have the icon functionality. Therefore, if Now onto step 5, overriding the protected override void Render(HtmlTextWriter writer)
{
writer.RenderBeginTag(HtmlTextWriterTag.Span);
base.Render(writer);
RequiredTextBoxValidator.RenderControl(writer);
writer.RenderEndTag();
}
I chose to enclose the whole control in a Note: Take a look at the overridden OK, so that's basically it for the In the validator, rather than re-write all of Microsoft validation routines and debug a mile of JavaScript, I took the easy way out and followed these steps:
So here is the complete validator class, we will break it down some more: [
DefaultProperty("Text"),
ToolboxItem(false),
]
public class RequiredValidator : RequiredFieldValidator
{
public RequiredValidator(RequiredTextBox textbox)
: base()
{
TextBox = textbox;
}
private RequiredTextBox m_TextBox = null;
private RequiredTextBox TextBox
{
get { return m_TextBox; }
set
{
this.ID = value.ClientID + "_RequiredValidator";
m_TextBox = value;
}
}
public bool RenderDesignModeErrorProvider
{
get
{
if (ViewState["RenderDesignModeErrorProvider"] == null)
{
ViewState["RenderDesignModeErrorProvider"] = true;
}
return (bool)ViewState["RenderDesignModeErrorProvider"];
}
set { ViewState["RenderDesignModeErrorProvider"] = value; }
}
protected override void Render(HtmlTextWriter writer)
{
if (TextBox.ErrorProvider == ErrorProviderType.Text)
{
base.Render(writer);
}
else
{
if (!TextBox.IsDesignMode || (TextBox.IsDesignMode &&
RenderDesignModeErrorProvider))
{
string src = string.Empty;
if (TextBox.ErrorProvider == ErrorProviderType.StillIcon)
{
src = TextBox.Page.ClientScript.GetWebResourceUrl(
TextBox.GetType(),
"click2install.resource.errorprovider.gif");
}
else if (TextBox.ErrorProvider ==
ErrorProviderType.AnimatedIcon)
{
src = TextBox.Page.ClientScript.GetWebResourceUrl(
TextBox.GetType(),
"click2install.resource.errorprovider_anim.gif");
}
Text = " <img src=\"" + src + "\"" +
" alt=\"" + ErrorMessage + "\"" +
" title=\"" + ErrorMessage + "\"" +
" />";
base.Render(writer);
}
}
}
}
As we can see from the code above, we don't want this showing up in our Toolbox ( Also, from the code above, we can see that we pass in a reference to the The approach taken above simply replaces the *normal* To embed an image (that is, generally speaking, or to add more to this solution), you have to:
If we look at the above OK, so that's basically it. A working So here is my Smart Tag that accompanies the code:
To add this functionality, we need to perform a few elementary steps:
OK, so let's start with the Designer class. namespace click2install.design
{
public class RequiredTextBoxDesigner : ControlDesigner
{
private DesignerActionListCollection m_ActionLists = null;
public override DesignerActionListCollection ActionLists
{
get
{
if (m_ActionLists == null)
{
m_ActionLists = base.ActionLists;
m_ActionLists.Add(new RequiredTextBoxActionList(
(RequiredTextBox)Component));
}
return m_ActionLists;
}
}
}
}
Pretty straightforward, yeah. We override the function, and return a new And here is a snippet from the namespace click2install.design
{
public class RequiredTextBoxActionList : DesignerActionList
{
private RequiredTextBox m_LinkedControl = null;
public RequiredTextBoxActionList(RequiredTextBox textbox) :
base(textbox)
{
m_LinkedControl = textbox;
}
private PropertyDescriptor GetPropertyByName(string name)
{
PropertyDescriptor pd =
TypeDescriptor.GetProperties(m_LinkedControl)[name];
if (null == pd)
{
throw new ArgumentException("Property '" + name +
"' not found on " + m_LinkedControl.GetType().Name);
}
return pd;
}
public string ValidatorMessage
{
get { return m_LinkedControl.ValidatorMessage; }
set { GetPropertyByName("ValidatorMessage").SetValue(
m_LinkedControl, value); }
}
public bool ValidatorFocusOnError
{
get { return m_LinkedControl.ValidatorFocusOnError; }
set { GetPropertyByName("ValidatorFocusOnError").SetValue(
m_LinkedControl, value); }
}
..
.. more properties into the TextBox here
..
public ErrorProviderType ErrorProvider
{
get { return m_LinkedControl.ErrorProvider; }
set { GetPropertyByName("ErrorProvider").SetValue(m_LinkedControl, value); }
}
private void LaunchSite()
{
try { System.Diagnostics.Process.Start("http://www.click2install" +
".com/programming/#RequiredTextBox"); }
catch { }
}
public override DesignerActionItemCollection GetSortedActionItems()
{
DesignerActionItemCollection coll = new DesignerActionItemCollection();
coll.Add(new DesignerActionHeaderItem("Validator"));
coll.Add(new DesignerActionPropertyItem("ValidatorMessage",
"Validator Message:", "Validator",
"The required validators Message"));
coll.Add(new DesignerActionPropertyItem("ValidatorFocusOnError",
"Focus on Error", "Validator",
"The required validators SetFocusOnError"));
coll.Add(new DesignerActionPropertyItem("ValidatorEnableClientScript",
"Enable Client Script", "Validator",
"The required validators EnableClientScript"));
coll.Add(new DesignerActionPropertyItem("ValidatorDisplayType",
"Validator Display Type", "Validator",
"The required validators Display"));
coll.Add(new DesignerActionPropertyItem("ErrorProvider",
"Error Display Type", "Validator",
"The error providers display type"));
coll.Add(new DesignerActionHeaderItem("Design Mode"));
coll.Add(new DesignerActionPropertyItem("RenderDesignModeValidatorIcon",
"Render DesignMode Icons", "Design Mode",
"Set true to render error provider icons in DesignMode"));
coll.Add(new DesignerActionHeaderItem("Information"));
coll.Add(new DesignerActionMethodItem(this, "LaunchSite",
"RequiredTextBox Website ...", "Information", true));
coll.Add(new DesignerActionTextItem("ID: " + m_LinkedControl.ID, "ID"));
return coll;
}
}
}
The code above, although wordy, is fairly self-explanatory, especially if you like intellisense. From this code though, you can see I have added a nice little utility function that allows me to easily access the properties of the host control to which this Smart Tag belongs to, namely, In closing, I hope this control is of some use to someone, as either a starting point to stir your creative juices to create custom Server Controls, or to just use this control in your applications, or just to explain and outline some of the inner workings of Custom Controls design. Points of interestAn interesting thing that I learnt a long time ago when I first starting creating Custom Server Controls is to (during development only) wrap your render functions or control creation functions (like Also helpful is to override the History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||