Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1241 Chapter 26: User and Server Controls Now that you can create a postback, you may want to add events to your control that execute dur- ing the page postback. To raise server-side events from a client-side object, you implement the Sys- tem.Web.IPostBackEventHandler interface. Listing 26-31 shows how to do this for a button control. You also create a server-side Click event you can handle when the page posts back. Listing 26-31: Handling postback events in a server control VB Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Text Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls <DefaultProperty("Text")> _ <ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")> _ Public Class ServerControl1 Inherits System.Web.UI.WebControls.WebControl Implements IPostBackEventHandler ’. . . Code removed for clarity . . . Public Event Click() Public Sub OnClick(ByVal args As EventArgs) RaiseEvent Click() End Sub Public Sub RaisePostBackEvent(ByVal eventArgument As String) _ Implements System.Web.UI.IPostBackEventHandler.RaisePostBackEvent OnClick(EventArgs.Empty) End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace ServerControl1 { [DefaultProperty("Text")] [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] public class ServerControl1 : WebControl, IPostBackEventHandler { //. . . Code removed for clarity . . . #region IPostBackEventHandler Members 1241 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1242 Chapter 26: User and Server Controls public event EventHandler Click; public virtual void OnClick(EventArgs e) { if (Click != null) { Click(this,e); } } public void RaisePostBackEvent(string eventArgument) { OnClick(EventArgs.Empty); } #endregion } } Now, when the user clicks the button and the page posts back, the server-side Click event fires, allowing you to add server-side handling code to the event. Handling PostBack Data Now that you have learned how to store data in ViewState and add postback capabilities to a control, look at how you can enable the control to interact with data the user enters into one of its form fields. When a page is posted back to the server by ASP.NET, all the form data is also posted to the server. If the control can interact with data that is passed with a page, you can store the information in ViewState and complete the illusion of a stateful application. To interact with postback data, your control must be able to access the data. To do this, it implements the System.Web.IPostBackDataHandler interface. This interface allows your control to examine the form data that is passed back to the server during the postback. The IPostBackDataHandler interface requires that you implement two methods: LoadPostData and RaisePostBackDataChangedEvent .The LoadPostData method is called for all server controls on the page that have postback data. If a control does not have any postback data, the method is not called; however, you can explicitly ask for the method to be called by using the RegisterRequiresPostBack method. Listing 26-32 shows how you implement the IPostBackDataHandler interface method in a text box. Listing 26-32: Accessing Postback data in a server control VB Imports System Imports Sustem.Collections.Generic Imports System.ComponentModel Imports System.Text Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls <DefaultProperty("Text")> _ 1242 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1243 Chapter 26: User and Server Controls <ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")> _ Public Class ServerControl1 Inherits SystemWebControl Implements IPostBackEventHandler, IPostBackDataHandler ’. . . Code removed for clarity . . . Public Function LoadPostData(ByVal postDataKey As String, _ ByVal postCollection As _ System.Collections.Specialized.NameValueCollection) _ As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData Me.Text = postCollection(postDataKey) Return False End Function Public Sub RaisePostDataChangedEvent() _ Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace ServerControl1 { [DefaultProperty("Text")] [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] public class ServerControl1 : WebControl, IPostBackEventHandler, IPostBackDataHandler { //. . . Code removed for clarity . . . public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection) { this.Text = postCollection[postDataKey]; return false; } public void RaisePostDataChangedEvent() { } } } 1243 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1244 Chapter 26: User and Server Controls As you can see, the LoadPostData method passes any form data submitted to the method as a name value collection that the control can access. The postDataKey parameter allows the control to access the postback data item specific to it. You use these parameters to save text to the Text property of the TextBox control. If you remember the earlier ViewState example, the Text property saves the new value to ViewState; when the page renders, the TextBox value automatically repopulates. In addition to the input parameters, the LoadPostData method also returns a Boolean value. This value indicates whether the RaisePostBackDataChangedEvent method is also called after the LoadPostData method completes execution. In the sample, it returns false because no events exist, but if you create a TextChanged event to indicate the Textbox text has changed, you raise that event in the RaisePostDataChangedEvent method. Composite Controls So far, in looking at Server controls, you have concentrated on emitting a single HTML control; but this can be fairly limiting. Creating extremely powerful controls often requires that you nest several HTML elements together. ASP.NET allows you to easily create controls that serve as a container for other controls.Thesetypesofcontrolsarecalledcomposite controls. To demonstrate how easy creating a composite control can be, try to change an existing control into a composite control. Listing 26-33 shows how you can do this. Listing 26-33: Creating a composite control VB Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Text Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls <DefaultProperty("Text")> _ <ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")> _ Public Class ServerControl1 Inherits System.Web.UI.WebControls.CompositeControl Protected textbox As TextBox Protected Overrides Sub CreateChildControls() Me.Controls.Add(textbox) End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web; 1244 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1245 Chapter 26: User and Server Controls using System.Web.UI; using System.Web.UI.WebControls; namespace ServerControl1 { [DefaultProperty("Text")] [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] public class ServerControl1 : CompositeControl { protected TextBox textbox = new TextBox(); protected override void CreateChildControls() { this.Controls.Add(textbox); } } } A number of things in this listing are important. First, notice that the control class is now inheriting from CompositeControl , rather than WebControl . Deriving from CompositeControl gives you a few extra features specific to this type of control. Second, notice that no Render method appears in this code. Instead, you simply create an instance of another type of server control and add that to the Controls collection in the CreateChildControls method. When you run this sample, you see that it renders a text box just like the last control did. In fact, the HTML that it renders is almost identical. Exposing Child Control Properties When you drop a composite control (such as the text box from the last sample) onto the design sur- face, notice that even though you are using a powerful ASP.NET TextBox control within the control, none of that control’s properties are exposed to you in the Properties Explorer. In order to expose child control properties through the parent container, you must create corresponding properties in the parent control. For example, if you want to expose the ASP.NET text box Text property through the parent control, you create a Text property. Listing 26-34 shows how to do this. Listing 26-34: Exposing control properties in a composite control VB Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Text Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls <DefaultProperty("Text")> _ <ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")> _ Public Class ServerControl1 Inherits System.Web.UI.WebControls.CompositeControl Protected textbox As TextBox Public Property Text() As String 1245 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1246 Chapter 26: User and Server Controls Get EnsureChildControls() Return textbox.Text End Get Set(ByVal value As String) EnsureChildControls() textbox.Text = value End Set End Property Protected Overrides Sub CreateChildControls() Me.Controls.Add(textbox) Me.ChildControlsCreated=True End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace ServerControl1 { [DefaultProperty("Text")] [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] public class ServerControl1 : CompositeControl { protected TextBox textbox = new TextBox(); public string Text { get { EnsureChildControls(); return textbox.Text; } set { EnsureChildControls(); textbox.Text = value; } } protected override void CreateChildControls() { this.Controls.Add(textbox); this.ChildControlsCreated=true; } } } 1246 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1247 Chapter 26: User and Server Controls Notice that you use this property simply to populate the underlying control’s properties. Also notice that before you access the underlying control’s properties, you always call the EnsureChildControls method. This method ensures that children of the container control have actually been initialized before you attempt to access them. Templated Controls In addition to composite controls, you can also create templated controls. Templated controls allow the user to specify a portion of the HTML that is used to render the control, and to nest other controls inside of a container control. You might be familiar with the Repeater or DataList control. These are both templated controls that let you specify how you want the bound data to be displayed when the page renders. To demonstrate a templated control, the following code gives you a simple example of displaying a mes- sage from a user on a Web page. Because the control is a templated control, the developer has complete control over how the message is displayed. To get started, create the Message server control that will be used as the template inside of a container control. Listing 26-35 shows the class which simply extends the existing Panel control by adding two additional properties, Name and Text, and a new constructor. Listing 26-35: Creating the templated control’s inner control class VB Public Class Message Inherits System.Web.UI.WebControls.Panel Implements System.Web.UI.INamingContainer Private _name As String Private _text As String Public Sub New(ByVal name As String, ByVal text As String) _text = text _name = name End Sub Public ReadOnly Property Name() As String Get Return _name End Get End Property Public ReadOnly Property Text() As String Get Return _text End Get End Property End Class C# using System; using System.Text; using System.Web; 1247 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1248 Chapter 26: User and Server Controls using System.Web.UI; using System.Web.UI.WebControls; namespace ServerControl1 { public class Message : Panel, INamingContainer { private string _name; private string _text; public Message(string name, string text) { _text = text; _name = name; } public string Name { get { return _name;} } public string Text { get { return _text;} } } } As you will see in a moment, you can access the public properties exposed by the Message class in order to insert dynamic content into the template. You will also see how you can display the values of the Name and Text properties as part of the rendered template control. Next, as shown in Listing 26-36, create a new server control which will be the container for the Message control. This server control is responsible for rendering any template controls nested in it. Listing 26-36: Creating the template control container class VB Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Text Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls <DefaultProperty("Text")> _ <ToolboxData("<{0}:TemplatedControl runat=server></{0}:TemplatedControl>")> _ Public Class TemplatedControl Inherits System.Web.UI.WebControls.WebControl Private _name As String Private _text As String 1248 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1249 Chapter 26: User and Server Controls Private _message As Message Private _messageTemplate As ITemplate <Browsable(False)> Public ReadOnly Property Message() As Message Get Return _message End Get End Property <PersistenceMode(PersistenceMode.InnerProperty), _ TemplateContainer(GetType(Message))> _ Public Property MessageTemplate() As ITemplate Get Return _messageTemplate End Get Set(ByVal value As ITemplate) _messageTemplate = value End Set End Property <Bindable(True), DefaultValue("")> Public Property Name() As String Get Return _name End Get Set(ByVal value As String) _name = value End Set End Property <Bindable(True), DefaultValue("")> Public Property Text() As String Get Return _text End Get Set(ByVal value As String) _text = value End Set End Property Public Overrides Sub DataBind() CreateChildControls() ChildControlsCreated = True MyBase.DataBind() End Sub Protected Overrides Sub CreateChildControls() Me.Controls.Clear() _message = New Message(Name, Text) If Me.MessageTemplate Is Nothing Then Me.MessageTemplate = New DefaultMessageTemplate() End If 1249 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1250 Chapter 26: User and Server Controls Me.MessageTemplate.InstantiateIn(_message) Controls.Add(_message) End Sub Protected Overrides Sub RenderContents( _ ByVal writer As System.Web.UI.HtmlTextWriter) EnsureChildControls() ChildControlsCreated = True MyBase.RenderContents(writer) End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace ServerControl1 { [DefaultProperty("Text")] [ToolboxData("<{0}:TemplatedControl runat=server></{0}: TemplatedControl >")] public class TemplatedControl : WebControl { private string _name; private string _text; private Message _message; private ITemplate _messageTemplate; [Browsable(false)] public Message Message { get { return _message; } } [PersistenceMode(PersistenceMode.InnerProperty)] [TemplateContainer(typeof(Message))] public virtual ITemplate MessageTemplate { get { return _messageTemplate;} set { _messageTemplate = value;} } 1250 . called by using the RegisterRequiresPostBack method. Listing 26 -32 shows how you implement the IPostBackDataHandler interface method in a text box. Listing 26 -32 : Accessing Postback data in a server. System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace. template inside of a container control. Listing 26 - 35 shows the class which simply extends the existing Panel control by adding two additional properties, Name and Text, and a new constructor. Listing