Create the Date Table Custom Control

The DateTable Custom control displays a date of your choice in an HTML table cell. By default, the control looks like Figure 23.6 in the designer. I took this screenshot after double-clicking in the ToolBox to add the control without sizing it or setting any properties.

Not too exciting, is it? But it's a powerful concept. Even with this plain example, you can immediately see that a major difference between a Custom control and a User control is that you can provide a customizable design-time user interface for a Custom control, but you can't for a User control. The control displays the current date by default. For this example, the control always displays the date in LongDateTime format—that's easy enough to change if you want to extend the control.

I—«!-]

I*

!.■>! (m; Cff-3 Tfwvr TzA

U*

1 M-

■jbí

t L - " - £ - ' - 1

- -M

m il

Ep __

-j ^ M t U

a ¿

[i > ■ *

r^fui

-3

AMI

l^fp*«, » i

3k. .

MM .!

-KXiia

■ i- j1 j

a

jJ i"ri

3

i c

■ww «-i

a

tfc.i

fcr-W-^.ta '-T''--

im f

WpcN

c-ul

E! ■■■

■r-u- 4 ■

-»¿«hi-«,

1 J

Mr*-. rwtp

■3

Figure 23.6: DateTable Custom control default UI characteristics Next, here's the Properties window for the DateTable control (see Figure 23.7).

Figure 23.7: DateTable Custom Control Properties window

The default property for the control is called Date, and it has a default date value (the current date) as soon as you add the control. Custom controls inherit a reasonable set of properties from the System.Web.UI.WebControl class—most of the properties shown are inherited. You control which properties you want to display in the Properties window via Property attributes.

Because the control supports design-time properties, by setting those properties, you can radically alter the look of the control (see Figure 23.8).

Monday. April 01, 2002

Figure 23.8: DateTable Custom control after setting design-time properties

Despite the fact that Custom controls inherit some default design-time features, such as BackColor, BorderColor, BorderWidth, Font, CssClass, and so forth, you do have to write some other common properties. Listing 23.5 shows the code for the DateTable class.

Listing 23.5: Code for the DateTable Custom Control (CSharpASPCustom.DateTable.cs)

using System;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.ComponentModel;

using System.Drawing;

namespace CSharpASPCustom

/// Summary description for DateTable. /// </summary> public enum TextAlign { left = 1, right = 2, center = 3, justify = 4

public enum TextVAlign { top = 1, middle = 2, bottom = 3, baseline = 4, subscript = 5, superscript = 6, text top = 7, text bottom = 8

[ Description("Exposes a Date in an HTML Table tag")] [ DefaultProperty("Date"), ToolboxData("<{0}:DateTable " + "runat=server style='position: absolute; width:300: " + "height: 100;' />")] public class DateTable : System.Web.UI.WebControls.WebControl, INamingContainer {

private DateTime mDate = new DateTime(); private String mNow = DateTime.Now.ToLongDateString(); private TextAlign mAlign = TextAlign.center; private TextVAlign mVAlign = TextVAlign.middle;

[Bindable(true), Category("Appearance"),

DefaultValue("Monday, January 1, 0001")] public String Date { get {

return DateTime.Now.ToLongDateString();

return mDate.ToLongDateString();

mDate = DateTime.Parse(value);

[Bindable(true), Category("Appearance"), DefaultValue("center")]

public TextAlign Align { get {

return mAlign;

mAlign = value;

private String AlignText { get {

return TextAlign.GetName(mAlign.GetType(), mAlign);

[Bindable(true), Category("Appearance"), DefaultValue("middle")]

public TextVAlign VAlign { get {

return mVAlign;

mVAlign = value;

private String VAlignText { get {

if (mVAlign == TextVAlign.subscript) { return "sub";

else if (mVAlign == TextVAlign.superscript) { return "super";

return TextVAlign.GetName(mVAlign.GetType(), mVAlign);

protected override void Render

(System.Web.UI.HtmlTextWriter output) { HtmlTable tbl = new HtmlTable(); HtmlTableRow row = new HtmlTableRow(); HtmlTableCell cell = new HtmlTableCell(); String aValue; tbl.Style.Add(" color",

System.Drawing.ColorTranslator.ToHtml(this.ForeColor)); tbl.Style.Add(" background-color",

System.Drawing.ColorTranslator.ToHtml(this.BackColor)); tbl.Style.Add(" border-color",

System.Drawing.ColorTranslator.ToHtml(this.BorderColor));

tbl.Style.Add(" border-style", this.BorderStyle.ToString());

tbl.Style.Add(" border-width", this.BorderWidth.ToString());

tbl.Style.Add(" width", this.Width.ToString()); tbl.Style.Add(" height", this.Height.ToString()); tbl.Style.Add(" text-align", this.AlignText); cell.Style.Add(" vertical-align", this.VAlignText); if (this.Font != null) {

if (this.Font.Names.Length > 0) { tbl.Style.Add(" font-family",

ArrayJoin.Join(this.Font.Names));

tbl.Style.Add(" font-size", this.Font.Size.ToString()); if (this.Font.Bold) {

tbl.Style.Add(" font-weight", "bold");

tbl.Style.Add(" font-style", "italic");

if (this.Font.Overline) {

tbl.Style.Add(" font-decoration", "overline");

if (this.Font.Strikeout) {

tbl.Style.Add(" font-decoration", "line-through");

if (this.Font.Underline) {

tbl.Style.Add(" font-decoration", "underline");

if (this.Attributes.CssStyle != null) {

foreach (String key in this.Attributes.CssStyle.Keys) { aValue = this.Attributes.CssStyle[key]; tbl.Attributes.CssStyle.Add(" " + key, aValue);

cell.InnerText = this.Date;

tbl.Rows.Add(row);

row.Cells.Add(cell);

tbl.RenderControl(output);

You can see that the class implements only three public properties: Date, Align, and VAlign. It defines text Align and text VAlign enumerations and uses those as the property type values for the private Align and VAlign properties, respectively.

Note that (other than the Property attributes) there's no special code to support design-time properties or any Property Window special code or types; all that is built into the .NET framework. For string properties, all you need to do is make sure the properties you want to expose to the designer are defined as public properties.

Tip The default designer handles simple types such as strings, but for more complex property types such as arrays of strings or custom property types, you'll need to set the Designer attribute to the proper type or create a custom designer class or interface—but that's beyond the scope of this book.

In fact, most of the code is in the overridden Render method. The Render method creates HtmlTable, HtmlRow, and HtmlCell objects and then assigns style attributes and values based on the properties the user set in the HTML tag in the Web Form, or using the Properties window to render the HTML for the control. After setting up the table, the method writes the table using the RenderControl method, passing the HtmlTextWriter received by the overridden Render method as the only parameter.

The RenderControl method writes an HTML string containing a single-row/single-column HTML <table> tag.

More About Attributes

Attributes are classes in .NET. Each of the built-in attribute class names ends with the word "Attribute," but in the shorthand version shown, you don't have to include the Attribute portion. For example, as shown in Listing 23.5, the class attribute list looks like this:

[DefaultProperty("Date"), ToolboxData("<{0}:DateTable " + "runat=server style= position: absolute; width:300: " + "height: 100;' />")]

Tip To search for an attribute in Help or the Object Browser, remember to append the word "Attribute" to the end of the class name.

If you used the full attribute class names rather than the default shorthand version, you could write this:

[DefaultPropertyAttribute("Date"), ToolboxDataAttribute("<{0}:DateTable " + "runat=server style='position: absolute; width:300: " + "height: 100;' />")]

The DefaultProperty attribute declares that the class DateTable has a default property called Text. Just as in the LabeledTextBox control code you saw earlier in the chapter, the ToolboxData attribute controls the initial tag that the designer places into the Web Form HTML when you drop a control onto the design surface.

Note If you have a VB background, the DefaultProperty attribute replaces the rather quirky (and well-hidden) method used in classic VB to define a default property for a class. That's true for all classes in .NET, not just classes created as Web controls.

It's worth noting that attributes aren't required for Custom controls—they're all optional. You can delete all the default attributes without adverse effects.

You can add as many tag attributes as needed to the ToolboxData class attribute.

The property attributes affect both design-time and runtime aspects of the control. Design-time properties are those that appear in the Properties window when a user adds your control to a Web Form at design time. Again, the designer uses the shorthand form of the attribute class names by default. Remember that the full names of these attribute classes are BindableAttribute, Category-Attribute and DefaultValueAttribute.

The Bindable attribute controls whether you can bind the property to a data value. The Category attribute controls in which area or category of the Properties window a property appears. It's not always clear which area you should specify. For example, does the Date value belong in the Appearance category or the Data category? At any rate—you get to choose. Although the built-in categories usually suffice, you can create custom categories if necessary.

The DefaultValue attribute, of course, is the default value of the control. That property accepts only hard-coded values. For example, you can't call a function to set a derived value as the default control value using a property attribute. In other words, this DefaultValue attribute is acceptable:

[DefaultValue("This is some text")] // property declaration here

In contrast, the following version is not acceptable and will not compile:

[DefaultValue(getDefaultValue())] // property declaration here private String getDefaultValue() { return "This is some text";

Despite that, you can work around it by creating an "impossible" default value and then handling that condition explicitly in code. For example, the default Date property specifies a date of "Monday,

January 1, 0001".

[Bindable(true), Category("Appearance"), DefaultValue("Monday, January 1, 0001")] public String Date {

// implementation here

However, as you can see in the figures, the default date that appears in the control is the current date. This is the workaround for not being able to specify a DefaultValue and run the DateTime.Now.To-LongDateString method. The Date property implementation explicitly checks for the condition Year == 1. When that evaluates to true, the property returns the current date. It's true that that means users wouldn't be able to use the control to set an explicit date in the year 0001— but that's an acceptable risk.

public String Date { get {

return DateTime.Now.ToLongDateString();

return mDate.ToLongDateString();

mDate = DateTime.Parse(value);

Although you won't see any more information about attributes in this book, attributes are an extremely powerful concept. .NET contains a large number of predefined attribute classes, which you should explore thoroughly. If the built-in attribute classes don't meet your needs, you can define your own custom attribute classes. Attributes aren't limited to classes and properties; you can apply attributes to assemblies, constructors, delegates, enums, events, fields, interfaces, parameters, return values, and structs.

There are several interesting features related to using the control in the designer. First, look at the Web Form ch23-5.aspx in the VS designer. The Web Form has a single DateTable control. Try selecting the DateTable control and then entering an invalid date for the Date property. You'll see an error message, but it doesn't stop the program. The error message (see Figure 23.9) is automatic—you get property value type-checking and conversion for free.

Figure 23.9: Automatic property error dialog appears after an invalid date is set.

Another interesting feature is that the Properties window seamlessly accommodates the enumeration types. Click the Align or VAlign property, and you get a list of values corresponding to the appropriate enumeration names. Again, this feature is free—you don't have to add special code to make it work.

In the Render method, you access properties set by the control user by retrieving them from the class itself, using the this keyword. The .NET framework helps a great deal by being able to transform color selections to their HTML equivalents using the ColorTranslator class's static ToHtml method. The ColorTranslator changes the colors either to common color names, such as red, or to the HTML color representation, such as #cococo.

System.Drawing.ColorTranslator.ToHtml(this.ForeColor))

Because the font-family CSS attribute can accept a list of font names, the Properties window lets you enter more than one font name. If a client browser can't display the first font in the list, it tries subsequent font names in sequence until it finds one it can use. If none of the named fonts are available, the browser displays the control's text content in the browser's default font. The font names the control user enters appear as a Font property called Names, which returns an array of string objects. You need to turn that array into a comma-delimited string. In classic ASP, using VBScript, you could use the Join method to accomplish that. In .NET, the Array class doesn't have a Join method, but you can write a small helper class to iterate through the array and accomplish the task:

If Me.Font.Names.Length > 0 Then tbl.Style.Add(" font-family", _ ArrayJoin.Join(Me.Font.Names))

End If

The ArrayJoin class (in the CSharpASPCustom project) accepts an array and returns a comma-delimited string object containing a string representation (using the ToString() method inherited from Object) of each element in the array (see Listing 23.6).

Listing 23.6: Code for the ArrayJoin Helper Class (CSharpASPCustom.ArrayJoin.cs)

using System;

using System.Text;

using System.Collections;

namespace CSharpASPCustom {

public class ArrayJoin {

public ArrayJoin() {

public static String Join(Array arr) {

StringBuilder sb = new StringBuilder(arr.Length *

(arr.GetValue(0).ToString().Length + 1)); for (int i = 0; i < arr.Length; i++) { if (i < arr.Length - 1) {

sb.Append(arr.GetValue(i).ToString() + ",");

sb.Append(arr.GetValue(i).ToString());

return sb.ToString();

Was this article helpful?

0 0

Post a comment