13.2. Custom Controls


So far, you have created user controls, which are essentially reusable web page fragments.[*] You can also create your own compiled custom controls . As noted earlier, there are three ways to create custom controls :

[*] In fact, user controls were originally called "Pagelets" a far better name in my opinion, but no one at Microsoft asked my opinion.

  • Create a derived custom control by deriving from an existing control.

  • Create a composite control by grouping existing controls together into a new control.

  • Create a full custom control by deriving from System.Web.UI.WebControls.WebControl.

The custom controls most similar to user controls are the composite controls. The key difference is that composite controls are compiled into a DLL and used as you would any server control you find in the Toolbox.

13.2.1. Creating a Web Control Library

To get started, you'll create a Web Control Library in which you'll place the various custom controls for this chapter. Open Visual Studio .NET and choose New Project. In the New Project Window, create a Web Control Library called CustomControls, as shown in Figure 13-4.

After clicking OK, you'll notice that Visual Studio has created a complete custom control named WebCustomControl1 . Before examining this control, create a web application to test it. Right-click on the solution in the Solution explorer and choose Add New Web Site. Set the name of your web site to CustomControlTester. Your solution now includes two projects: a web site (with Default.aspx) and a Custom Controls library (with WebCustomControl1.vb).

13.2.2. Web Custom Control 1

Web Custom Control 1 is a full custom control, derived from System.Web.UI.WebControls.WebControl. Even before you fully understand how this code works, you

Figure 13-4. New Web Control Library


can test it in the test page you created. Open Default.aspx in the tester application and add a statement to register the new control:

     <%@Register TagPrefix="OReilly"     Namespace="CustomControls"     Assembly="CustomControls" %> 

This registers the custom control with the web page, similar to how you registered your user controls. Once again you use the @Register tag and provide a tag prefix (OReilly). Rather than providing a Tagname and src, however, you provide a Namespace and Assembly, which uniquely identify the control and the DLL that the page must use.

You now add the control to the page. The three attributes you must set are the Runat and ID attributes, which are needed for all server-side controls, and the Text attribute, which this custom control uses to determine how the control is displayed at runtime:

     <OReilly:WebCustomControl1 Runat="Server" Text="Hello World!"  /> 

To build your new custom control, you must inform the CustomControlTester project about the CustomControls project. To do so, right-click on the CustomControlTester project and choose Add Reference. The Add Reference dialog opens. Select the Project tab, and choose the CustomControls project, as shown in Figure 13-5.

Click OK and then compile and run your test application. You will be asked to add a new Web.config file. As usual, click OK. When you view the page, the text you passed in as an attribute ("Hello World"):

     <OReilly:WebCustomControl1 Runat="Server" Text="Hello World!"  /> 

is displayed, as shown in Figure 13-6.

Figure 13-5. Add Reference to custom controls


Figure 13-6. Testing default custom control


Before proceeding, let's examine the code in WebCustomControl1.vb, created by Visual Studio 2005. This control contains a single property, Text, backed by a private string variable, _text:

     Dim _text As String     <Bindable(True), Category("Appearance"), DefaultValue("")>_     Property [Text]( ) As String         Get             Return _text         End Get         Set(ByVal Value As String)             _text = Value         End Set     End Property 

Notice that there are attributes (see the sidebar "Attributes") provided both for the property and for the class. These attributes are used by Visual Studio .NET and are not required when creating custom controls. The most common attributes for custom controls are shown in Table 13-2.

Table 13-2. Common attributes for custom controls

Attribute

Description

Bindable

Boolean. TRue indicates that VS.NET will display this control in the data bindings dialog box.

Browsable

Boolean. Is the property displayed in the designer?

Category

Determines in which category this control will be displayed when the Properties dialog is sorted by category.

DefaultValue

The default value.

Description

The text you provide is displayed in the description box in the Properties panel.


Attributes

Attributes are metadata that is, information about the data itself. Often this data is used by tools (e.g., Visual Studio.NET).

There are two interesting facts about attributes:

  • The metadata is compiled into the program (rather than into an associated file).

  • You can examine that data either at runtime (using a technique called reflection) or independently using tools such as ILDASM.

Because all of this is fairly obscure, attributes and reflection are not covered in this book, except in so far as they are needed to accomplish specific tasks (such as COM interop, covered in Chapter 7).


13.2.3. Properties

Custom controls can expose properties just as any other class can. You access these properties in two ways:

  1. Programmatically (e.g., in the code-behind)

  2. Declaratively, by setting attributes of the custom control, as shown here:

         <OReilly:WebCustomControl1 Runat="Server" Text="Hello World!" /> 

The Text property of the control is accessed through the Text attribute in the web page.

In the case of the Text property and the Text attribute, the mapping between the attribute and the underlying property is straightforward because both are strings.

ASP.NET will provide intelligent conversion of other types, however. For example, if the underlying type is an integer or a long, the attribute will be converted to the appropriate value type. If the value is an enumeration, ASP.NET matches the string value against the enumeration name and sets the correct enumeration value. If the value is a Boolean, ASP.NET matches the string value against the Boolean value; that is, it will match the string "True" to the Boolean value true.

13.2.4. The Render Method

The key method of the custom control is Render :

     Protected Overrides Sub Render( _     ByVal output As System.Web.UI.HtmlTextWriter)         output.Write([Text])     End Sub 

This method is declared in the base class, and must be overridden in your derived class if you wish to take control of rendering to the page. In this example, the Render method uses the HtmlTextWriter object passed in as a parameter to write the string held in the Text property.

The HtmlTextWriter class derives from TextWriter and provides rich formatting capabilities. HtmlTextWriter will ensure that the elements produced are well-formed, and it will manage the attributes, including style attributes. Thus, if you want to set the text to red, you can add a color attribute, passing in an enumerated color object that you've translated to HTML, as shown in Example 13-2.

Example 13-2. Overriding the Render method
 Protected Overrides Sub Render( _ ByVal output As System.Web.UI.HtmlTextWriter)     output.AddStyleAttribute("color", ColorTranslator.ToHtml(Color.Red))     output.Write([Text]) End Sub 

For the new line of code to compile, you will need to add an Imports statement at the top of the source code:

     Imports System.Drawing 


13.2.5. Rendering Text with Tags

You can set the text to be rendered within header (<h2>) tags with the HtmlTextWriter's RenderBeginTag and RenderEndTag methods:

     output.RenderBeginTag("h2")     output.Write(Text)     output.RenderEndTag( ) 

The result is that when the text is output, the correct tags are created, as shown in Figure 13-7. (The source output that illustrates the HTML rendered by the HtmlTextWriter is circled.)

13.2.6. Maintaining State

In the next example, you'll add a button to increase the size of the text in your custom control. To accomplish this, you'll eschew the rendering support of the

Figure 13-7. Output and source


HtmlTextWriter, instead writing the text yourself, using a new Size property (to set the size of the output text). Return to the Render method and replace the code you have with this new code.

     Protected Overrides Sub Render(ByVal output As _                                    System.Web.UI.HtmlTextWriter)        output.Write("<font size = " & Size & ">" & [Text] & "</font>")     End Sub 

The Size property must maintain its state through the postback initiated by pressing the button.

     Public Property Size( ) As Integer        Get           Return Convert.ToInt32(ViewState("Size"))        End Get        Set(ByVal Value As Integer)           ViewState("Size") = Value.ToString( )        End Set     End Property 

The property Get method retrieves the value from ViewState, casts it to a string, and then converts that string to its integer equivalent. The property Set method stashes a string representing the size into ViewState.

To ensure that a valid value is in ViewState to start with, you'll also add a constructor to this control:

     Public Sub New( )        ViewState("Size") = "1"     End Sub 

The constructor initializes the value held in ViewState to 1. Each press of the button will update the Size property. To make this work, drag a button onto CustomControlTester.Default.aspx, next to the custom control. Set its text to Increase Size and double-click on it to open the default (click) event handler.

     Protected Sub Button1_Click( _     ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click         WC1.Size += 1     End Sub 

Each time the button is clicked, the state variable Size is incremented; when the page is drawn, the state variable is retrieved and used to set the size of the text, as shown by creating a copy of the running program in Figure 13-8.

Figure 13-8. Demonstrating event handling with custom controls


13.2.7. Creating Derived Controls

Most custom controls need not be created from scratch. If you are doing anything more than writing text, it is far easier to use the rendering capabilities of one or more existing controls.

Often, all you want to do is to extend the behavior of an existing control type. To do so, you will derive from that 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.

Create a new class in your custom controls library named CountedButton, as shown in Figure 13-9.

The Add New Item - CustomControls dialog will pop up. Select Web Custom Control and name it CountedButton.vb, as shown in Figure 13-10.

A new custom class is created for you. By default, it will inherit from System.Web.UI.WebControls.WebControl. Change the Inherits statement so it derives from System.Web.UI.WebControls.Button.

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

This will allow your new custom control to inherit all the features of the standard button, which you can then extend. Your new class needs a Count property to keep track of the number of times the button is clicked. Since Count must survive the

Figure 13-9. Adding new class to custom control library


Figure 13-10. Creating CountedButton class


roundtrip to the server, you must store it in either Session or view state. Because the number of clicks is really a property of the CountedButton (rather than a value you might want to pass from page to page or persist for the life of the session), it is most appropriate to use view state, as shown in Example 13-3.

Example 13-3. Count property
 Public Property Count( ) As Integer     Get         Return CInt(ViewState("Count"))     End Get     Set(ByVal Value As Integer)         ViewState("Count") = Value     End Set End Property 

Remember to initialize the Count to 0 in the constructor:

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

Notice that the constructor also sets the Text property of the CountedButton (inherited from the base class button) to "Click Me".

The CountedButton also inherits, and must override, the OnClick event. Be careful here: you are not implementing an event handler, you are overriding an event declared in the base class. To do so, type Protected Overrides, and IntelliSense will offer all members of the base class that you are free to override. Choose OnClick, as shown in Figure 13-11.

Figure 13-11. Overriding OnClick event


When you select OnClick, the entire event is set up for you, including a call to MyBase.OnClick and the capitalization is fixed for you as well. This is just what you want. You'll put in your own mechanism to update the counter and to update the button's text, and then you'll call the base class (Button's) OnClick event to allow it to do whatever work it normally does.

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

When you extract the object whose key is "Count" from ViewState, what you get back is of type Object. You cast it to integer using CInt, then add 1 to that value. Finally, you store it back into ViewState using the same key: "Count".

     ViewState("Count") = CInt(ViewState("Count")) + 1 

Once you have the new count in ViewState, you can update the text of the button to reflect the number of clicks:

     Me.Text = ViewState("Count") & " clicks" 

13.2.8. Adding the Derived Custom Control to the ASPX Page

Return to the ASPX page to add an instance of your counted button. Since you've already registered your CustomControl library, you only have to add the actual CountedButton itself to the form:

     <div>         <OReilly:WebCustomControl1 Runat="Server" Text="Hello World!"  />         <asp:Button  runat="server" Text="Increase Size" />         <OReilly:CountedButton Runat="Server"  />      </div> 

When you run the application, you can click on the button, and it keeps track of the number of times it was clicked, as shown in Figure 13-12.

Figure 13-12. Testing the CountedButton


13.2.9. Creating Composite Controls

The third way to create a custom control is to combine two or more existing controls into a single bundled control. You can even combine custom controls with the controls provided by Microsoft.

In the next example, you'll create a somewhat advanced composite control to keep track of the number of inquiries you receive about books (perhaps books you sell on your web site). The architecture of your custom control is as follows:

  • The BookInquiryList is a custom composite control that serves as a collection of BookCounter controls.

  • The BookCounter is a custom control with two properties: BookName and Count.

  • The (revised) CountedButton control is a derived control (derived from Button).

The BookCounter has an instance of CountedButton as a member in its Controls collection. The BookInquiryList has, in its Controls collection, zero or more instances of BookCounter.

The finished product is shown in Figure 13-13.

Figure 13-13. BookInquiryList


What is most interesting about this project is that you will declare the BookCounter members of the BookInquiry control declaratively in your aspx page. Just as you might write:

     <asp:ListBox  runat="server">         <asp:ListItem>Item 1</asp:ListItem>         <asp:ListItem>Item 2</asp:ListItem>         <asp:ListItem>Item 3</asp:ListItem>     </asp:ListBox> 

so, with the BookInquiry control, you can write:

     <OReilly:BookInquiryList Runat="Server" >        <OReilly:BookCounter Runat="server" BookName="Book 1"  />        <OReilly:BookCounter Runat="server" BookName="Book 2"  />        <OReilly:BookCounter Runat="server" BookName="Book 3"  />     </OReilly:BookInquiryList> 

This kind of declaration requires that you teach the ASP.NET parser how to create BookCounter objects based on their declaration; a topic covered later in this chapter. The best way to build this, however, is inside out: starting with the CountedButton control, then using that to build the BookCounter, and finally populating the BookInquiry control with BookCounter objects.

13.2.10. Modifying the CountedButton Derived Control

Modify the CountedButton class so that the client of your Countedbutton (any method that creates a CountedButton object) can pass in the string to be displayed on the button. That is, rather than displaying "5 clicks," the client can pass in (for example) the string "Inquiries" and the button will display "5 Inquiries."

Reopen the CountedButton.vb file and add a class member displayString of type String:

     Private displayString As String 

Modify the constructor to initialize displayString to the string "clicks" if no string is supplied, by creating both a default constructor (that takes no arguments) and a constructor that takes a string argument, as shown in Example 13-4.

Example 13-4. CountedButton constructors
 Public Sub New( )     Me.New("clicks") End Sub Public Sub New(ByVal displayString As String)     Me.displayString = displayString     If ViewState("Count") Is Nothing Then         ViewState("Count") = 0         Me.Text = "Click me"     End If End Sub 

Rather than duplicating code in both constructors, the bulk of the work is done in the second constructor. The default constructor invokes the second constructor, passing in the string clicks. Modify the OnClick event to use the display string in its display, as shown in Example 13-5.

Example 13-5. CountedButton OnClick event handler
 Protected Overrides Sub OnClick(ByVal e As System.EventArgs)      ViewState("Count") = CInt(ViewState("Count")) + 1      Me.Text = ViewState("Count") & " " & displayString      MyBase.OnClick(e) End Sub 

With these changes, the CountedButton is ready to be used in the first composite control, BookCounter .

13.2.11. Creating the BookCounter Control

The BookCounter control is responsible for keeping track of and displaying the number of inquiries about an individual book. It does no rendering, it simply "holds" a book name (using ViewState) and a counted button (which is responsible for its own count).

Create a new class called BookCounter in your custom controls library, of type Web Custom Control, just as you did for CountedButton.

Replace the _text member variable with _countedButton of type CountedButton:

     Dim _countedButton As CountedButton = New CountedButton("Inquiries") 

Delete the methods provided by Visual Studio 2005 and add a property to hold the book name, as shown in Example 13-6.

Example 13-6. BookName property BookName property
 Public Property BookName( ) As String     Get         Return CStr(ViewState("BookName"))     End Get     Set(ByVal Value As String)         ViewState("BookName") = Value     End Set End Property 

The book name will be held in ViewState so that it persists across postback events. Book counter needs a second property, Count , but you'll implement Count by delegating responsibility to the member variable _countedButton, as shown in Example 13-7.

Example 13-7. Count property Count property
 Public Property Count( ) As Integer     Get         Return _countedButton.Count     End Get     Set(ByVal Value As Integer)         _countedButton.Count = Value     End Set End Property 

You'll need a method to reset the CountedButton's count to 0, shown here in Example 13-8.

Example 13-8. Reset method of Book counter
 Public Sub Reset( )     _countedButton.Count = 0 End Sub 

In a moment, you'll create the BookInquiryList class which will consist of zero or more BookCounter objects. To make sure that the CountedButton is a control within each BookCounter object created, you'll need to add _countedButton to the Controls collection of your BookCounter control. You do so by overriding the CreateChildControls method:

     Protected Overrides Sub CreateChildControls( )         Controls.Add(_countedButton)     End Sub 

CreateChildControls is called in preparation for rendering and offers the BookCounter class the opportunity to add the CountedButton 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 don't need to do anything special to make this work.

13.2.12. INamingContainer

Because your BookCounter class contains a control, you must implement the INamingContainer interface. This is a "marker" interface that has no methods. The purpose of this interface is specifically to identify a control as a container control to ASP.NET. By implementing I Naming Container you instruct ASP.NET to create a new ID namespace for your BookCounter control, guaranteeing that all child controls have IDs that are unique to the page.

     Public Class BookCounter     Inherits System.Web.UI.WebControls.WebControl     Implements INamingContainer 

For more on interfaces, please see Chapter 16.


To make this crystal clear, Example 13-9 has the complete definition of the BookCounter class.

Example 13-9. BookCounter class
 Public Class BookCounter Inherits System.Web.UI.WebControls.WebControl Implements INamingContainer     Dim _countedButton 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 _countedButton.Count         End Get         Set(ByVal Value As Integer)             _countedButton.Count = Value         End Set     End Property     Public Sub Reset( )         _countedButton.Count = 0     End Sub     Protected Overrides Sub CreateChildControls( )         Controls.Add(_countedButton)     End Sub End Class 

13.2.13. Creating the BookInquiryList Composite Control

You have now created a CountedButton and a custom control, BookCounter, that holds the name of a book and an instance of CountedButton within it. All of this will be wrapped within a BookInquiryList control that will be designed to hold zero or more BookCounter instances.

To start, create a new custom control: BookInquiryList. Strip out the attributes created by Visual Studio 2005 and strip out all the code within the class. Once again, have your class implement INamingContainer:

     Public Class BookInquiryList     Inherits System.Web.UI.WebControls.WebControl _     Implements INamingContainer 

13.2.14. Declaring the BookCounters in the .aspx File

As noted above, you want to be able to declare a BookInquiryList in your aspx page, and then to declare BookCounter elements within the BookInquiry list, just as you do with Lists and ListItems. You can type this right into the aspx file itself.

     <OReilly:BookInquiryList Runat="Server" >        <OReilly:BookCounter Runat="server" BookName="Book 1"  />         <OReilly:BookCounter Runat="server" BookName="Book 2"  />         <OReilly:BookCounter Runat="server" BookName="Book 3"  />     </OReilly:BookInquiryList> 

Making this work with a Visual Studio collection editor is a much more advanced topic beyond the scope of this book.


For this to work, the ASP.NET parser must know how to create the BookCounter objects within the BookInquiryList. You accomplish this in two steps:

  1. Declare a class BookCounterBuilder that inherits from ControlBuilder, and that knows how to "build" an instance of BookCounter (more on that below).

  2. Add an attribute to the declaration of BookInquiryList indicating where to find the BookCounterBuilder.

To get started, create your BookCounterBuilder. It will inherit from ControlBuilder and override just two methods: GetChildControlType and AppendLiteralString. The latter will be left empty; its job is just to have the AppendLiteralString method do nothing.

     Public Overrides Sub AppendLiteralString(ByVal s As String)     End Sub 

The override of GetChildControlType is slightly more complicated. It is passed in a tagName by the ASP.NET parser (the tag you declare in the .aspx page) and a dictionary of attributes (attributes you add to the declaration in the .aspx page).

In this case, all you need to do is examine the tag name. If it is "BookCounter," you create a BookCounter object and return its type by calling its inherited GetType method:

     Public Overrides Function GetChildControlType( _           ByVal tagName As String, ByVal attributes As IDictionary) As Type         If tagName = "BookCounter" Then             Dim theBookCounter As BookCounter             Return theBookCounter.GetType         Else             Return Nothing         End If     End Function 

With the BookCounterBuilder created, you are ready to add the required attributes to the BookInquiryList:

             <ControlBuilder(GetType(BookCounterBuilder)), ParseChildren(False> _     Public Class BookInquiryList     Inherits System.Web.UI.WebControls.WebControl _     Implements INamingContainer 

The ControlBuilder attribute specifies the ControlBuilder class for building a custom control within the ASP.NET parser that will parse your aspx page. The second attribute, ParseChildren, must be set to False. If the value were TRue, you'd be signaling that the nested values were properties of the object, rather than child controls.

13.2.15. Implementing BookInquiryList.Render

All that is left is to override the Render method. You'll start by declaring a local variable to keep track of the total number of clicks of all the buttons:

     Dim totalClicks As Integer = 0 

You'll then write out a table, complete with a header:

     output.Write("<Table border='1' width='90%' cellpadding='1'" & _        "cellspacing='1' align = 'center' >")     output.Write("<TR><TD colspan = '2' align='center'>")     output.Write("<B> Inquiries </B></TD></TR>") 

You next create a table row for each BookCounter object you'll add. To determine how many books to add, you'll extract all the BookCounter objects in the BookInquiryList's Controls collection. Each time you extract one, you'll ask it for its count (which will, you remember, pass the query along to its counted button). You will then call RenderControl on the BookCounter, and the control will be rendered to the page within the table cell tags.

     For Each current As BookCounter In Controls         totalClicks += current.Count         output.Write("<TR><TD align='left'>" & _            current.BookName + "</TD>")         output.RenderBeginTag("TD")         current.RenderControl(output)         output.RenderEndTag( )            ' end td         output.Write("</tr>")     Next 

Once you are done, you'll add one more row for the total:

     Dim strTotalInquiries As String = totalClicks.ToString( )     output.Write("<TR><TD colspan='2' align='center'> " & _        " Total Inquiries: " & _        CStr(strTotalInquiries) & "</TD></TR>") 

The complete Render method is shown in Example 13-10.

Example 13-10. Complete BookInquiryList.Render method
 Protected Overrides Sub Render(ByVal output As HtmlTextWriter)     Dim totalClicks As Integer = 0     ' Write the header     output.Write("<Table border='1' width='90%' cellpadding='1'" & _        "cellspacing='1' align = 'center' >")     output.Write("<TR><TD colspan = '2' align='center'>")     output.Write("<B> Inquiries </B></TD></TR>")     ' if you have no contained controls, write the default msg.     If Controls.Count = 0 Then         output.Write("<TR><TD colspan='2' align='center'>")         output.Write("<B> No books listed </B></TD></TR>")         ' otherwise render each of the contained controls     Else         ' iterate over the controls collection and         ' display the book name for each         ' then tell each contained control to render itself         'Dim current As BookCounter         For Each current As BookCounter In Controls             totalClicks += current.Count             output.Write("<TR><TD align='left'>" & _                current.BookName + "</TD>")             output.RenderBeginTag("TD")             current.RenderControl(output)             output.RenderEndTag( )            ' end td             output.Write("</tr>")         Next         Dim strTotalInquiries As String = totalClicks.ToString         output.Write("<TR><TD colspan='2' align='center'> " & _            " Total Inquiries: " & _            CStr(strTotalInquiries) & "</TD></TR>")     End If     output.Write("</TABLE>") End Sub 

You are now ready to declare an instance of BookInquiryList within Default.aspx, adding any number of BookCounter entries, as shown in Example 13-11.

Example 13-11. Default.aspx with BookInquiryList declaration
 <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="Default_aspx" %> <%@Register TagPrefix="OReilly" Namespace="CustomControls" Assembly="CustomControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/ xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server">     <title>Custom Control Tester</title> </head> <body>     <form  runat="server">     <div>         <OReilly:WebCustomControl1 Runat="Server" Text="Hello World!"  />         <asp:Button  runat="server" Text="Increase Size" />         <OReilly:CountedButton Runat="Server"  />         <br /> <h2>Book Inquiry List </h2>       <OReilly:BookInquiryList Runat="Server" >          <OReilly:BookCounter Runat="server"          BookName="Programming Visual Basic 2005"          />          <OReilly:BookCounter Runat="server"          BookName="Programming ASP.NET"          />          <OReilly:BookCounter Runat="server"          BookName="Programming C#"           />          <OReilly:BookCounter Runat="server"          BookName="C#: A Developer's Notebook"           />          <OReilly:BookCounter Runat="server"          BookName="Programming .NET Windows Applications"           />          <OReilly:BookCounter Runat="server"          BookName="Teach Yourself C++ in 21 Days"           />          <OReilly:BookCounter Runat="server"          BookName="Teach Yourself C++ in 24 Hours"           />          <OReilly:BookCounter Runat="server"          BookName="Clouds to Code"           />       </OReilly:BookInquiryList>      </div>     </form> </body> </html> 

13.2.16. Assignment of Responsibilities

In this composite control the various responsibilities are spread among the participating objects illustrating good encapsulation. The BookInquiryList object assumes all responsibility for laying out the control, creating the table, and deciding what will be rendered where. However, it delegates responsibility for rendering the button object to the individual contained controls.

Similarly, the BookInquiryList is responsible for the total number of inquiries because that information transcends what any individual BookCounter object might know. However, the responsibility for the count held by each BookCounter is delegated to the BookCounter itself. As far as the BookInquiryList is concerned, it gets that information directly from the BookCounter's Count property. It turns out, however, that BookCounter, in turn, delegates that responsibility to the CountedButton.



Programming Visual Basic 2005
Programming Visual Basic 2005
ISBN: 0596009496
EAN: 2147483647
Year: 2006
Pages: 162
Authors: Jesse Liberty

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net