Creating Derived Controls

There are times when it is not necessary to create your own control from scratch. You may simply want to extend the behavior of an existing control type. You can derive from an existing control just as you might derive from any class.

Imagine, for example, that you would like a button to maintain a count of the number of times it has been clicked. Such a button might be useful in any number of applications, but unfortunately the web Button control does not provide this functionality.

To overcome this limitation of the button class, you'll derive a new custom control from System.Web.UI.WebControls.Button, as shown in Example 14-15 (for C#) and Example 14-16 (for VB.NET).

The work of this class is to maintain its state: how many times the button has been clicked. You provide a public property, Count, which is backed not by a private member variable but rather by a value stored in view state. This is necessary because the button will post the page, and the state would otherwise be lost. The Count property is defined as follows in C#:

Because CountedButton derives from Button, it is easy to override the behavior of a Click event. In this case, when the user clicks the button, you will increment the Count value held in view state and update the text on the button to reflect the new count. You will then call the base class' OnClick method to carry on with the normal processing of the Click event. The C# event handler is as follows:

You add this control to the .aspx form just as you would your composite control:

<OReilly:CountedButton Runat="Server" id="CB1" />

You do not need to add an additional Register statement because this control, like the custom control, is in the CustomControls namespace and the CustomControls assembly.

When you click the button four times, the button reflects the current count of clicks, as shown in Figure 14-14.

Figure 14-14.Counted button

Creating Composite Controls

The third way to create a custom control is to combine two or more existing controls. In the next example, you will act as a contract programmer, and I will act as the client. I'd like you to build a slightly more complex control that I might use to keep track of the number of inquiries I receive about my books.

As your potential client, I might ask you to write a control that lets me put in one or more books, and each time I click on a book the control will keep track of the number of clicks for that book, as shown in
Figure 14-15.

Figure 14-15.Composite control

The .aspx file for this program is shown in Example 14-17. Its C# and VB versions are identical, except for the @ Page directive.

The key thing to note in this code is that the BookInquiryList component contains a number of BookCounter elements. There is one BookCounter element for each book I wish to track in the control. The control is quite flexible. I can track one, eight (as shown here), or any arbitrary number of books. Each BookCounter element has a BookName attribute that is used to display the name of the book being tracked.

You can see from Figure 14-15 that each book is tracked using a CountedButton custom control, but you do not see a declaration of the CountedButton in the .aspx file. The CountedButton control is entirely encapsulated within the BookCounter custom control.

The entire architecture therefore is as follows:

The BookInquiry composite control derives from WebControl and implements INamingContainer, as described shortly.

The BookInquiry control has a Controls property that it inherits from the Control class (through WebControl) and that returns a collection of child controls.

Within this Controls collection is an arbitrary number of BookCounter controls.

BookCounter is itself a composite control that derives from WebControl and that also implements INamingContainer.

Each instance of BookContainer has two properties, BookName and Count.

The Name property is backed by view state and is initialized through the BookName BookName in the .aspx file

The Count property delegates to a private CountedButton object, which is instantiated in BookContainer.CreateChildControls( ).

The BookInquiry object has only two purposes: it acts as a container for the BookCounter objects, and it is responsible for rendering itself and ensuring that its contained BookCounter objects render themselves on demand.

The best way to see how all this works is to work your way through the code from the inside out. The most contained object is the CountedButton.