Download the Source
Creating your own controls is a great way to leverage the .NET Framework in
your applications. You can extend existing controls with your own properties
and behavior, combine controls together into composite controls, and even create
your own control from scratch. Furthermore, by using client-side JavaScript
with our controls, we can achieve a powerful level of interactivity and sophistication
without having to constantly post back to the server. In this article, I'll
walk through creating a customized ASP.NET text box control that changes background
color when it gains and loses focus. Our new "pimped-out" text box will include:
- Color highlighting when users enter data into the text box (courtesy of
some client-side JavaScript)
- Logic to gracefully handle browsers that don't support our highlighting
- Design-time support for the background color properties, in the Visual Studio
.NET (VS .NET) Property window and on the Designer surface
Let's start by creating a blank VS .NET project, and then add an ASP.NET WebApplication
project (I named mine WebApplication) and a Web Control Library
project (I named mine ControlLib) to the solution. Rename the
WebForm1.aspx file in the WebApplication to container.aspx;
rename the WebCustomControl1.cs file in the Web Control Library project to PimpedOutTextbox.cs.
It's always good to have meaningful names for your files! Our container.aspx
page will host the custom control once we've compiled it. Our Solution Explorer
should look like FIGURE 1.
FIGURE 1: The new files in Solution Explorer
Let's add some code to our PimpedOutTextbox class.
Since this is a Web Control Library project, VS .NET automatically adds the
"using" statements we'll need. After the using statements, replace the namespace
and class definition with the following code:
line 1: [assembly: TagPrefix ( "ControlLib" , "lib" )]
line 2: namespace ControlLib
line 3: {
line 4: [DefaultProperty( "BackColorOn" ),
line 5: ToolboxData( "<{0}:PimpedOutTextbox runat=server></{0}:PimpedOutTextbox>" )]
line 6: public class PimpedOutTextbox : System.Web.UI.WebControls.TextBox
line 7: {
|
Starting at the top, I've added an assembly attribute
to the namespace that will control the TagPrefix VS .NET
uses when we drag and drop our control onto a Web form. It doesn't impact our
control functionality, just the way it gets generated in VS .NET (more about this
later).
We decorate our PimpedOutTextbox class with some additional
attributes: DefaultProperty and ToolboxData.
DefaultProperty controls which control property is selected
by default when your control is hosted in the designer (we will define the BackColorOn
property shortly). The ToolboxData attribute works in
conjunction with the [assembly: TagPrefix] attribute
to dictate how our control is generated in the VS .NET HTML view. Again, I'll
have more to say about these attributes later; for now, just make sure your control
class includes them.
Finally, on line 6, note that our control uses the ":"
character to indicate we're inheriting from the WebControls.TextBox
class. Our control will extend the behavior of the text box. Among other things,
this means all the properties and behavior that a regular text box has will be
available in our control (unless we explicitly override them!).
The next step is to add our two additional properties to the PimpedOutTextbox
control. We want the user to set a color for when the control has the focus and
users are typing in it (named BackColorOn), and for when
the control loses the focus (named BackColorOff):
line 1: private Color _colOff;
line 2: [Category( "Appearance" ), Description( "The background color when the control loses focus" )]
line 3: public Color BackColorOff
line 4: {
line 5: get{return _colOff;}
line 6: set{_colOff = value
line 7: }
line 8: private Color _colOn;
line 9: [Category( "Appearance" ), Description( "The background color when the control has the focus" )]
line 10: public Color BackColorOn
line 11: {
line 12: get{return _colOn; }
line 13: set{_colOn = value;}
line 14: }
|
These are just basic property statements; nothing too fancy besides the Category
and Description attributes on lines 2 and 9 (these control
how the property is presented in the Property window of the VS .NET designer).
Note that we're using a Color data type instead of a
more simple string. The Color type will allow users of
our control to select colors from the VS .NET color chooser instead of having
to type their own hexadecimal or string values.
Now for some good stuff. The TextBox control from which
we're inheriting uses a method called AddAttributesToRender()
to add additional output to the browser. We're going to override this method to
insert our own code for the client-side onFocus and onBlur
JavaScript events. The other thing to note about this method is that the VS .NET
designer calls this method when creating the control at design time, so we can
also achieve design time support for our control through this method:
line 1: protected override void AddAttributesToRender( HtmlTextWriter writer )
line 2: {
line 3: base.AddAttributesToRender( writer );
line 4: //only add the client-side javascript for design mode or IE
line 5: if( inDesignMode() || System.Web.HttpContext.Current.Request.Browser.Type.IndexOf( "IE" ) > -1 )
line 6: {
line 7: writer.AddAttribute( "onFocus", "JavaScript:this.style.backgroundColor='" + ColorTranslator.ToHtml( _colOn ) + "';" );
line 8: if( _colOff.Equals( Color.Empty ) )
line 9: {
line 10: _colOff = this.BackColor;
line 11: }
line 12: writer.AddAttribute( "onBlur", "JavaScript:this.style.backgroundColor='" + ColorTranslator.ToHtml( _colOff ) + "';" );
line 13: }
line 14: }
|
To begin, line 3 invokes the base AddAttributesToRender
method in the TextBox class — this ensures our
control has all the functionality of the parent TextBox
object. The C# base object is how derived classes use
properties and methods in a parent object. The AddAttributesToRender
method uses an HtmlTextWriter as a parameter; we call
the AddAttribute method on the HtmlTextWriter
to add our client-side code to the output stream. Lines 7 and 12 demonstrate our
use of this technique. Note that we pass two arguments to AddAttribute,
the first is the Html attribute and the second is the
value for the attribute (they render as Key=Value pairs in the generated HTML
like this: <input type="text" onBlur="JavaScript...">).
We're just building dynamic text to include with the HTML generated for our control.
Since Web browsers use a hexadecimal color (and some literal colors such as Lime
or Blue), we use the ColorTranslator class to convert
our .NET Color type to a Web browser equivalent (lines
7 and 12 ). The ColorTranslator class is a good one with
which to familiarize yourself. On line 8, we test to see if the
_colOff property is not set (Color.Empty) and
assign the regular BackColor value to our _colOff
variable. This has the subtle effect of setting the BackColorOff
property to the same color as the BackColor property
— a nice touch for the control user.
Note that we're detecting the browser type on line 5. Internet Explorer (IE) supports
our dynamic onFocus and OnBlur
event handling, but Netscape and others might balk at the JavaScript. To avoid
nasty JavaScript errors, we only apply our extra code for IE browsers. Of course,
you could conditionally branch based on any browser type and include any browser
specific code you need — in the interest of brevity, this example doesn't
take things that far.
We also want our color highlighting code to be included when in VS .NET design
mode, thus our boolean inDesignMode function:
line 1: private bool inDesignMode()
line 2: {
line 3: bool blnOut = false;
line 4: if( object.ReferenceEquals( System.Web.HttpContext.Current, null ) )
line 5: {
line 6: blnOut = true;
line 7: }
line 8: else
line 9: {
line 10: blnOut = false;
line 11: }
line 12: return blnOut;
line 13: }
|
Line 4 is where the real action is here. If there isn't an HttpContext
for us to work with, we can assume our control is in design mode and not being
served up over HTTP. If we didn't put this check in, our controls wouldn't display
as text boxes in the designer.
That's the tour of our PimpedOutTextbox. We should
build our Web control library project and move into the WebApplication project
to take our control for a test drive.
Testing the New Control
Before we can use the custom control in our project, we need to reference it
in our WebApplication. Right-click on References in the WebApplication and select
Add Reference. On the Projects tab, select the Web Control library and click
OK; this copies the assembly, the dll, with our control in it into the bin directory
of the WebApplication. To add the control to the Toolbox, right-click on the
Toolbox and select Add/Remove Items, as shown in FIGURE 2.
FIGURE 2: Adding our custom control to the Toolbox
Make sure you're on the .NET Framework Components tab and click the Browse
button to locate the assembly in the WebApplication's bin directory. The PimpedOutTextbox
control should automatically become selected in the Customize Toolbox window.
Click OK. The PimpedOutTextbox is now available for
us to click and drag onto our Web form (look for PimpedOutTextbox at the bottom
of your Toolbox control list).
Go ahead and add a PimpedOutTextbox to the Web form. Notice the Property page
in the designer automatically selects our BackColorOn
property (this is due to our [DefaultProperty()] attribute
in the control class). Also note the Description in the bottom of the Property
page — courtesy of our [Description] attribute.
Furthermore, if you switch to HTML view, you'll see the impact of our TagPrefix
and ToolboxData attributes. The Register Page Directive
reflects our assembly attribute, as does the text defining the custom control.
Back in design view, set the BackColor of the PimpedOutTextbox
to gray and notice that the BackColorOff property gets
assigned the same value (this only happens if BackColorOff
doesn't already have a value). Switch it back to white and notice that BackColorOff
doesn't change — this is due to the this.BackColorOff.Equals(
Color.Empty ) logic we added in our overridden AddAttributesToRender().
Next, set the BackColorOn property to any color other
than white and run your WebApplication to see the PimpedOutTextbox in action.
When the control has the focus, the background of the text box is highlighted.
When the focus leaves the control (firing the client-side onBlur
event in the browser), the background is set to the BackColorOff
property, as shown in FIGURE 3.
FIGURE 3: The WebApplication in action
You can try it with a non-IE browser and confirm that our conditional logic
is working — meaning there are no JavaScript errors (and no color highlighting).
We can go further and set a breakpoint in our AddAttributesToRender
method and step through the execution of our logic when a page loads on the
server; this is vital to sophisticated control debugging.
If you want to step through the client-side code, you can debug your JavaScript
code using VS .NET. Make sure Internet Explorer doesn't have client-side
debugging disabled. Then, add a debugger; statement
to where you want your JavaScript breakpoint to hit, like this:
|
JavaScript:debugger;this.style.backgroundColor='blue'; |
Run your application and try it out. The debugger;
statement acts like a client-side breakpoint. You'll be able to interrogate
variables and the document object model from the VS .NET immediate window. Try
it and type document.forms[0].all[1].name in the immediate
window. You'll get the name of the first control on the page (it's a zero-based
array, but the control at ordinal position zero is used for the hidden
__VIEWSTATE control). The more complicated the JavaScript, the more useful
this technique becomes.
I've only scratched the surface of control development and client-side script;
we could make the forecolor change or make the font-style become bold when the
text box has the focus or wrap our control in a span tag and assign a color
to the span for different events in the control's lifecycle. By unlocking the
door to client-side script in our ASP.NET controls, the possibilities are endless.
This PimpedOutTextbox is not "big pimpin'" — it's just "lil' pimpin'!"
I'll leave the other properties and extension points for you to implement. Just
be sure to not overwhelm the user — a little bit of JavaScript like this
goes a long way.