Programming ASP.NET: Custom and User Controls, Part 2
Pages: 1, 2, 3, 4, 5
Modifying the CountedButton derived control
CountedButton needs only minor modification, as shown in Example 14-18 for C# and Example 14-19 for VB.NET.
Example 14-18: The modified CountedButton.cs file
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
{
private string displayString;
// default constructor
public CountedButton( )
{
displayString = "clicks";
InitValues( );
}
// overloaded, takes string to display (e.g., 5 books)
public CountedButton(string displayString)
{
this.displayString = displayString;
InitValues( );
}
// called by constructors
private void InitValues( )
{
if (ViewState["Count"] == null)
ViewState["Count"] = 0;
this.Text = "Click me";
}
// count as property maintained in view state
public int Count
{
get
{
// initialized in constructor
// can not be null
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"] + " " + displayString;
base.OnClick(e);
}
}
}
Example 14-19: The modified CountedButton.vb file
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
Private displayString As String
' constructor initializes view state value
Public Sub New( )
displayString = "clicks"
Init( )
End Sub
' overloaded, takes string to display (e.g., 5 books)
Public Sub New(ByVal displayString As String)
Me.displayString = displayString
Init( )
End Sub
' called by constructors
Private Shadows Sub Init( )
If ViewState("Count") = Is Nothing Then
ViewState("Count") = 0
Me.Text = "Click me"
End If
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 = CStr(ViewState("Count") & " " & displayString
MyBase.OnClick(e)
End Sub
End Class
Because you want the button to be able to display the string 5 Inquiries rather than 5 clicks, you must change the line within the OnClick method that sets the button's text:
this.Text = ViewState["Count"] + " " + displayString;
The VB.NET equivalent is:
Me.Text = ViewState("Count") & " " & displayString
Rather than hard-wiring the string, you'll use a private member variable, displayString, to store a value passed in to the constructor:
private string displayString;
In VB.NET, you'd use:
Private displayString As String
You must set this string in the constructor. To protect client code that already uses the default constructor (with no parameters), you'll overload the constructor, adding a version that takes a string:
public CountedButton(string displayString){this.displayString = displayString;Init( );}
In VB.NET, the code is:
Public Sub New(ByVal displayString As String)Me.displayString = displayStringInitialize( )End Sub
You can now modify the default constructor to set the displayString member variable to a reasonable default value. In C#, the code is:
public CountedButton( ){displayString = "clicks";InitValues( );}
In VB.NET, use:
Public Sub New( )displayString = "clicks"Init( )End Sub
The code common to both constructors has been factored out to the private helper method Init, which ensures that the Count property is initialized to zero and sets the initial text for the button:
private void Init( ){if (ViewState["Count"] == null)ViewState["Count"] = 0;this.Text = "Click me";}
In VB.NET, the same thing is accomplished using:
Private Shadows Sub Init( )If ViewState("Count") = Nothing ThenViewState("Count") = 0Me.Text = "Click me"End IfEnd Sub
With these changes, the CountedButton is ready to be used in the first composite control, BookCounter.
Creating the BookCounter composite control
The BookCounter composite control is responsible for keeping track of and displaying the number of inquiries about an individual book. Its complete source code is shown in C# in Example 14-20 and in VB.NET in Example 14-21.
Example 14-20: BookCounter.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace CustomControls
{
public class BookCounter :
System.Web.UI.WebControls.WebControl,
INamingContainer
{
// intialize the counted button member
CountedButton btn = new CountedButton("inquiries");
public string BookName
{
get
{
return (string) ViewState["BookName"];
}
set
{
ViewState["BookName"] = value;
}
}
public int Count
{
get
{
return btn.Count;
}
set
{
btn.Count = value;
}
}
public void Reset( )
{
btn.Count = 0;
}
protected override void CreateChildControls( )
{
Controls.Add(btn);
}
}
}
Example 14-21: BookCounter.vb
Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.ComponentModel
Public Class BookCounter
Inherits System.Web.UI.WebControls.WebControl
Implements INamingContainer
' intialize the counted button member
Public btn As CountedButton = New CountedButton("inquiries")
Public Property BookName( ) As String
Get
Return CStr(ViewState("BookName"))
End Get
Set(ByVal Value As String)
ViewState("BookName") = Value
End Set
End Property
Public Property Count( ) As Integer
Get
Return btn.Count
End Get
Set(ByVal Value As Integer)
btn.Count = Value
End Set
End Property
Public Sub Reset( )
btn.Count = 0
End Sub
Protected Overrides Sub CreateChildControls( )
Controls.Add(btn)
End Sub
End Class
INamingContainer
The first thing to note about the BookCounter class is that it implements the INamingContainer interface. This is a "marker" interface that has no methods. The purpose of this interface is to identify a container control that creates a new ID namespace, guaranteeing that all child controls have IDs that are unique to the application.
Containing CountedButton
The BookCounter class contains an instance of CountedButton:
CountedButton btn = new CountedButton("inquiries");
or:
Public btn As CountedButton = New CountedButton("inquiries")
The btn member is instantiated in the CreateChildControls method inherited from System.Control:
protected override void CreateChildControls( ){Controls.Add(btn);}
The VB.NET equivalent is:
Protected Overrides Sub CreateChildControls( )Controls.Add(btn)End Sub
CreateChildControls is called in preparation for rendering and offers the BookCounter class the opportunity to add the btn object as a contained control.
There is no need for BookCounter to override the Render method; the only thing it must render is the CountedButton, which can render itself. The default behavior of Render is to render all the child controls, so you need not do anything special to make this work.
BookCounter also has two properties: BookName and Count. BookName is a string to be displayed in the control and is managed through ViewState. Its C# source code is:
public string BookName
{
get
{
return (string) ViewState["BookName"];
}
set
{
ViewState["BookName"] = value;
}
}
Its VB.NET source code is:
Public Property BookName( ) As String
Get
Return CStr(ViewState("BookName"))
End Get
Set(ByVal Value As String)
ViewState("BookName") = Value
End Set
End Property
Count is the count of inquires about this particular book; responsibility for keeping track of this value is delegated to the CountedButton. In C#, the code is:
public int Count
{
get
{
return btn.Count;
}
set
{
btn.Count = value;
}
}
and in VB.NET, it's:
Public Property Count( ) As Integer
Get
Return btn.Count
End Get
Set(ByVal Value As Integer)
btn.Count = Value
End Set
End Property
There is no need to place the value in ViewState, since the button itself is responsible for its own data.

