WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Programming ASP.NET: Custom and User Controls, Part 2
Pages: 1, 2, 3, 4, 5

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).

Example 14-15: CountedButton implementation in C#


using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
 
namespace CustomControls
{
   // custom control derives from button
   public class CountedButton : System.Web.UI.WebControls.Button
   {
 
      // constructor initializes view state value
      public CountedButton(  )
      {
        this.Text = "Click me";
         ViewState["Count"] = 0;
      }
   
      // count as property maintained in view state
      public int Count 
      {
         get
         {
            return (int) ViewState["Count"];
         }
 
         set
         {
            ViewState["Count"] = value;
         }
      }
 
      // override the OnClick to increment the count,
      // update the button text and then invoke the base method
      protected override void OnClick(EventArgs e)
      {
         ViewState["Count"] =  ((int)ViewState["Count"]) + 1;
         this.Text = ViewState["Count"] + " clicks";
         base.OnClick(e);
      }
   }
}

Example 14-16: CountedButton implementation in VB.NET


Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.WebControls
 
' custom control derives from button
Public Class CountedButton
   Inherits System.Web.UI.WebControls.Button
 
   ' constructor initializes view state value
   Public Sub New(  )
      Me.Text = "Click me"
      ViewState("Count") = 0
   End Sub
 
   ' count as property maintained in view state
   Public Property Count(  ) As Integer
      Get
         Return CInt(ViewState("Count"))
      End Get
      Set(ByVal Value As Integer)
         ViewState("Count") = Value
      End Set
   End Property
 
   ' override the OnClick to increment the count,
   ' update the button text and then invoke the base method
   Protected Overrides Sub OnClick(ByVal e As EventArgs)
      ViewState("Count") = CInt(ViewState("Count")) + 1
      Me.Text = ViewState("Count") & " clicks"
      MyBase.OnClick(e)
   End Sub
End Class

You begin by deriving your new class from the existing Button type:

public class CountedButton : System.Web.UI.WebControls.Button

The VB.NET equivalent is:

Public Class CountedButton
   Inherits System.Web.UI.WebControls.Button

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#:

public int Count 
{
   get
   {
      return (int) ViewState["Count"];
   }
 
   set
   {
      ViewState["Count"] = value;
   }
}

and it is defined as follows in VB.NET:

Public Property Count(  ) As Integer
   Get
   Return CInt(ViewState("Count"))
   End Get
   Set(ByVal Value As Integer)  
   ViewState("Count") = Value 
   End Set
   End Property

To retrieve the value "Count" from view state, you use the string Count as an offset into the ViewState collection. What is returned is an object that you cast to an int in C# or an Integer in VB.NET.

To ensure that the property will return a valid value, you initialize the Count property in the constructor, where you also set the initial text for the button. The constructor in C# is:

public CountedButton(  )
{ 
  this.Text = "Click me"; 
  ViewState["Count"] = 0;
}

and in VB.NET it appears as follows:

Public Sub New(  )
  Me.Text = "Click me"
  ViewState("Count") = 0
  End Sub

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:

protected override void OnClick(EventArgs e)
{
   ViewState["Count"] =  ((int)ViewState["Count"]) + 1;
   this.Text = ViewState["Count"] + " clicks";
   base.OnClick(e);
}

While the source code for the VB.NET Click event handler is:

Protected Overrides Sub OnClick(ByVal e As EventArgs)
   ViewState("Count") = CInt(ViewState("Count")) + 1
   Me.Text = ViewState("Count") & " clicks"
   MyBase.OnClick(e)
End Sub

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.

Example 14-17: The .aspx file for the composite control


<%@ Page language="c#" 
Codebehind="WebForm1.aspx.cs" 
AutoEventWireup="false" 
Inherits="CustomControlWebPage.WebForm1" %>
 
<%@ Register TagPrefix="OReilly" Namespace="CustomControls" Assembly="CustomControls" %>
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
  <HEAD>
<meta content="Microsoft Visual Studio 7.0" name=GENERATOR>
<meta content=C# name=CODE_LANGUAGE>
<meta content="JavaScript (ECMAScript)" name=vs_defaultClientScript>
<meta content=http://schemas.microsoft.com/intellisense/ie5 name=vs_targetSchema>
  </HEAD>
<body MS_POSITIONING="GridLayout">
<form id=Form1 method=post runat="server">
     
      <OReilly:BookInquiryList
      Runat="Server"
      id="bookInquiry1">
 
         <OReilly:BookCounter 
         Runat="server" 
         BookName="Programming ASP.NET" 
         ID="Bookcounter1"/>
   
         <OReilly:BookCounter 
         Runat="server" 
         BookName="Programming C#" 
         ID="Bookcounter2" />
   
         <OReilly:BookCounter 
         Runat="server" 
         BookName="Teach Yourself C++ 21 Days" 
         ID="BookCounter3" />
   
         <OReilly:BookCounter 
         Runat="server" 
         BookName="Teach Yourself C++ 24 Hours" 
         ID="Bookcounter4" />
   
         <OReilly:BookCounter 
         Runat="server" 
         BookName="Clouds To Code" 
         ID="Bookcounter5" />
   
         <OReilly:BookCounter 
         Runat="server" 
         BookName="C++ From Scratch" 
         ID="Bookcounter6" />
   
         <OReilly:BookCounter 
         Runat="server" 
         BookName="Web Classes From Scratch" 
         ID="Bookcounter7" />
   
         <OReilly:BookCounter 
         Runat="server" 
         BookName="XML Web Documents From Srcatch" 
         ID="Bookcounter8" />
   
      </OReilly:BookInquiryList>  
 
</FORM>      
  </body>
</HTML>

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:

  1. The BookInquiry composite control derives from WebControl and implements INamingContainer, as described shortly.
  2. The BookInquiry control has a Controls property that it inherits from the Control class (through WebControl) and that returns a collection of child controls.
  3. Within this Controls collection is an arbitrary number of BookCounter controls.
  4. BookCounter is itself a composite control that derives from WebControl and that also implements INamingContainer.
    1. Each instance of BookContainer has two properties, BookName and Count.
    2. The Name property is backed by view state and is initialized through the BookName BookName in the .aspx file
    3. 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.

Pages: 1, 2, 3, 4, 5

Next Pagearrow