July 7, 2011, 3:44 p.m.
posted by roni
Many controls contain other controls as part of their definition—these are termed composite controls. By embedding other controls within them, custom controls can be used to define "chunks" of forms that can potentially be reused from many different pages, complete with their own properties, events, and methods. Composite controls are built by creating child controls and adding them to the Controls collection of the parent control. All creation of child controls should take place in an override of the virtual function CreateChildControls. This function is inherited from the Control base class and is called at the correct time to create child controls (after the Load event, just before rendering).
1 Creating Child Controls
Listing 8-30 shows a simple composite control that implements a calculator by using three TextBox controls, a Button control, and some Literal Controls intermingled. A LiteralControl is a simple control that renders its Text property and is especially useful when building composite controls for properly laying out the child controls. This control also shows an example of hooking up an event handler to a child control.
Public Class CalcComposite Inherits Control Implements INamingContainer Private _operand1 As TextBox Private _operand2 As TextBox Private _result As TextBox Private Sub OnCalculate(sender As Object, _ e As EventArgs) Dim res As Integer res = Convert.ToInt32(_operand1.Text) + _ Convert.ToInt32(_operand2.Text) _result.Text = res.ToString() End Sub Public ReadOnly Property Result As Integer Get Return Convert.ToInt32(_result.Text) End Get End Property Protected Overrides Sub CreateChildControls() _operand1 = new TextBox() _operand1.ID = "Op1" _operand2 = new TextBox() _operand2.ID = "Op2" _result = new TextBox() _result.ID = "Res" Controls.Add(_operand1) Controls.Add(new LiteralControl(" + ")) Controls.Add(_operand2) Controls.Add(new LiteralControl(" = ")) Controls.Add(_result) Controls.Add(new LiteralControl("<br/>")) Dim calculate As Button = new Button() calculate.Text = "Calculate" AddHandler calculate.Click, _ New EventHandler(AddressOf Me.OnCalculate) Controls.Add(calculate) End Sub End Class
Notice also that the control shown in Listing 8-30 implements the INamingContainer interface. This is a marker interface (one with no methods) that indicates to the containing page that this control has child controls that may need to be in a separate namespace. If there is more than one instance of a composite control on a given page, it is important that the child controls of each composite control not have ID clashes. Anytime you have child controls in a custom control, you should be sure to add support for INamingContainer to avoid ID clashes in the rendered HTML.
You may find that in your composite control, you would like to manipulate some of your child controls during the Load event of your control. The CreateChildControls method is not called until after the Load event has fired, however, so if you attempt to access any child controls within a Load handler, you will find none. To guarantee that your child controls have been created, you can always call EnsureChildControls(). This function checks to see if the CreateChildControls function has been called yet (by checking the ChildControlsCreated Boolean), and if not, calls it for you. For example, the composite control shown in Listing 8-31 explicitly calls EnsureChildControls from within its Load handler before it tries to set one of the children's values.
Public Class CalcComposite Inherits Control Implements INamingContainer Private _operand1 As TextBox Private _operand2 As TextBox Private _result As TextBox Protected Sub Page_Load(src As Object, e As EventArgs) EnsureChildControls() _operand1.Text = "41" End Sub Protected Overrides Sub CreateChildControls() _operand1 = new TextBox() _operand2 = new TextBox() _result = new TextBox() '... End Sub End Class
With a composite control, it may also be useful to retrieve a child control dynamically. This is easily done with the FindControl method, which takes the string identifier of the control and returns a reference to the control.
2 Custom Events
For a custom control to truly provide all the attributes of standard Web controls, it must be able to define and propagate events. You define events in a custom control by adding a public event data member of delegate type EventHandler. A client then attaches a method to the event handler, and it is up to the control to invoke that method whenever the event logically occurs. Listing 8-32 shows a modified version of our CalcComposite control with a custom event added. Note that it declares a public EventHandler member called MagicNumber to which clients can hook delegates. In our example, the EventHandler is invoked whenever the user calculates the value 42 with the calculator. Listing 8-33 shows a sample .aspx page that traps the MagicNumber event and populates a label in response.
Public Class CalcComposite Inherits Control Implements INamingContainer ' other members not shown Public Event MagicNumber As EventHandler Private Sub OnCalculate(sender As Object, _ e As EventArgs) Dim res As Integer res = Convert.ToInt32(_operand1.Text) + _ Convert.ToInt32(_operand2.Text) _result.Text = res.ToString() If res = 42 Then RaiseEvent MagicNumber(Me, EventArgs.Empty) End If End Sub Protected Overrides Sub CreateChildControls() 'Other control creation not shown... Dim calculate As Button = new Button() calculate.Text = "Calculate" AddHandler calculate.Click, _ New EventHandler(AddressOf Me.OnCalculate) Controls.Add(calculate) End Sub End Class
<%@ Page Language="VB" %> <%@ Register TagPrefix="eadn" Namespace="EssentialAspDotNet.CustomControls" Assembly="CalcComposite" %> <html> <script runat=server> Private Sub MyCtrl_OnMagicNumber(src As Object, _ e As EventArgs) MagicNumberLabel.Text = "Magic number calculated!!" End Sub </script> <body> <form runat=server> <asp:Label id=MagicNumberLabel runat=server /> <eadn:SimpleComposite id="MyCtrl" OnMagicNumber="MyCtrl_OnMagicNumber" runat=server /> </form> </body> </html>