Listing 26-38: Creating the templated control’s default template class VB Friend Class DefaultMessageTemplate Implements ITemplate Public Sub InstantiateInByVal container As System.Web.U
Trang 1[DefaultValue("")]
public string Name
{
get { return _name;}
set { _name = value;}
}
[Bindable(true)]
[DefaultValue("")]
public string Text
{
get { return _text;}
set { _text = value;}
}
public override void DataBind()
{
CreateChildControls();
ChildControlsCreated = true;
base.DataBind();
}
protected override void CreateChildControls()
{
this.Controls.Clear();
_message = new Message(Name,Text);
if (this.MessageTemplate == null) {
this.MessageTemplate = new DefaultMessageTemplate();
} this.MessageTemplate.InstantiateIn(_message);
Controls.Add(_message);
}
protected override void RenderContents(HtmlTextWriter writer)
{
EnsureChildControls();
ChildControlsCreated = true;
base.RenderContents(writer);
}
}
}
To start to dissect this sample, first notice theMessageTemplateproperty This property allows Visual
Studio to understand that the control can contain a template, and allows it to display the IntelliSense
for that template The property has been marked with thePersistanceModeattribute indicating that
the template control should be persisted as an inner property within the control’s tag in the ASPX page Additionally, the property is marked with theTemplateContainerattribute, which helps ASP.NET figure out what type of template control this property represents In this case, it’s the Message template control you created earlier
1251
Trang 2The container control exposes two public properties, Name and Text These properties are used to
popu-late theNameandTextproperties of the Message control since that class does not allow developers to set
the properties directly
Finally, theCreateChildControlsmethod, called by theDataBindmethod, does most of the heavy
lifting in this control It creates a newMessageobject, passing the values ofNameandTextas constructor
values Once theCreateChildControlsmethod completes, the base DataBind operation comtinues to
execute This is important because that is where the evaluation of the Name and Text properties occurs,
which allows you to insert these properties values into the template control
After the control and template are created, you can drop them onto a test Web page Listing 26-37
shows how the control can be used to customize the display of the data
Listing 26-37: Adding a templated control to a Web page
VB
<%@ Page Language="VB" %>
<%@ Register Assembly="WebControlLibrary1" Namespace="WebControlLibrary1"
TagPrefix="cc1" %>
<script runat="server">
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
Me.TemplatedControl1.DataBind() End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Templated Web Controls</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:TemplatedControl Name="John Doe" Text="Hello World!"
ID="TemplatedControl1" runat="server">
<MessageTemplate>The user ’<%# Container.Name %>’
has a message for you: <br />"<%#Container.Text%>"
</MessageTemplate>
</cc1:TemplatedControl>
</div>
</form>
</body>
</html>
C#
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
this.TemplatedControl1.DataBind();
}
</script>
As you can see in the listing, the<cc1:TemplatedControl>control contains aMessageTemplatewithin
it, which has been customized to display theNameandTextvalues Figure 26-16 shows this page after it
Trang 3Figure 26-16
One item to consider when creating templated controls is what happens if the developer does not include
a template control inside of the container control In the previous example, if you removed the
Mes-sageTemplate control from the TemplateContainer, a NullReferenceException would occur when you
tried to run your Web page because the container control’s MessageTemplate property would return
a null value In order to prevent this, you can include a default template class as part of the container
control An example of a default template is shown in Listing 26-38
Listing 26-38: Creating the templated control’s default template class
VB
Friend Class DefaultMessageTemplate
Implements ITemplate
Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) _
Implements System.Web.UI.ITemplate.InstantiateIn
Dim l As New Literal()
l.Text="No MessageTemplate was included."
container.Controls.Add(l)
End Sub
End Class
C#
internal sealed class DefaultMessageTemplate : ITemplate
{
public void InstantiateIn(Control container)
{
Literal l = new Literal();
l.Text="No MessageTemplate was included.";
container.Controls.Add(l);
}
}
Notice that the DefaultMessageTemplate implements the ITemplate interface This interface requires that theInstantiateInmethod be implemented, which we use to provide the default template content
To include the default template, simply add the class to theTemplatedControlclass You will also
need to modify theCreateChildControlsmethod to detect the null MessageTemplate and instead create
an instance of and use the default template
1253
Trang 4If template = Nothing Then
template = New DefaultMessageTemplate()
End If
C#
if (template == null)
{
template = new DefaultMessageTemplate();
}
Creating Control Design-Time Experiences
So far in this chapter, you concentrated primarily on what gets rendered to the client’s browser, but
the browser is not the only consumer of server controls Visual Studio and the developer using a server
control are also consumers, and you need to consider their experiences when using your control
Note that beginning with Visual Studio 2008, the Web Page Design Surface used to provide Web page
designers with a WYSIWYG design experience has been completely rewritten The design-surface, which
in prior versions was derived from the core Internet Explorer rendering engine has been replaced by a
completely independent and new rendering engine This is good news for Web page developers because
they are no longer subject to the quirks of IE rendering If you have existing controls you should be
sure to test them thoroughly on the new design-surface to ensure compatibility From a control design
perspective, all of the previous functionality has been retained Therefore, any controls you have written
to take advantage of design-time tools such as SmartTags or Designer regions should function normally
on the new design surface.
ASP.NET offers numerous improvements in the design-time experience you give to developers using
your control Some of these improvements require no additional coding, such as the WYSIWYG
ren-dering of user controls and basic server controls; but for more complex scenarios, ASP.NET includes a
number of tools that give the developer an outstanding design-time experience
When you write server controls, a priority should be to give the developer a design-time experience
that closely replicates the runtime experience This means altering the appearance of the control on the
design surface in response to changes in control properties and the introduction of other server controls
onto the design surface Three main components are involved in creating the design-time behaviors of a
server control:
❑ Type Converters
❑ UI Type Editors
Because a chapter can be written for each one of these topics, in this section I attempt to give you only
an overview of each, how they tie into a control’s design-time behavior, and some simple examples of
their use
Type Converters
TypeConverteris a class that allows you to perform conversions between one type and another Visual
Studio uses type converters at design time to convert object property values to String types so that they
Trang 5can be displayed on the Property Browser, and it returns them to their original types when the developer changes the property
ASP.NET includes a wide variety of type converters you can use when creating your control’s design-time behavior These range from converters that allow you to convert most number types, to converters that let you convert Fonts, Colors, DataTimes, and Guids The easiest way to see what type converters are
available to you in the NET Framework is to search for types in the framework that derive from the
TypeConverterclass using the MSDN Library help
After you have found a type converter that you want to use on a control property, mark the property
with aTypeConverterattribute, as shown in Listing 26-39
Listing 26-39: Applying the TypeConverter attribute to a property
VB
<Bindable(True)> _
<Category("Appearance")> _
<DefaultValue("")> _
<TypeConverter(GetType(GuidConverter))> _
Property BookId() As System.Guid
Get
Return _bookid End Get
Set(ByVal Value As System.Guid)
_bookid = Value End Set
End Property
C#
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[TypeConverter(typeof(GuidConverter))]
public Guid BookId
{
get
{
return _bookid;
}
set
{
_bookid = value;
}
}
In this example, a property is exposed that accepts and returns an object of type Guid The Property
Browser cannot natively display a Guid object, so you convert the value to a string so that it can be
displayed properly in the property browser Marking the property with theTypeConverterattribute
and, in this case, specifying the GuidConverter as the type converter you want to use, allows complex
objects like a Guid to display properly in the Property Browser
1255
Trang 6Custom Type Converters
It is also possible to create your own custom type converters if none of the in-box converters fit into your
scenario Type converters derive from the System.ComponentModel.TypeConverterclass
Listing 26-40 shows a custom type converter that converts a custom object calledNameto and from
a string
Listing 26-40: Creating a custom type converter
VB
Imports System
Imports System.ComponentModel
Imports System.Globalization
Public Class Name
Private _first As String
Private _last As String
Public Sub New(ByVal first As String, ByVal last As String)
_first = first _last = last End Sub
Public Property First() As String
Get Return _first End Get
Set(ByVal value As String) _first = value
End Set End Property
Public Property Last() As String
Get Return _last End Get
Set(ByVal value As String) _last = value
End Set End Property
End Class
Public Class NameConverter
Inherits TypeConverter
Public Overrides Function CanConvertFrom(ByVal context As _
ITypeDescriptorContext, ByVal sourceType As Type) As Boolean
If (sourceType Is GetType(String)) Then Return True
End If
Return MyBase.CanConvertFrom(context, sourceType) End Function
Trang 7Public Overrides Function ConvertFrom( _
ByVal context As ITypeDescriptorContext, _
ByVal culture As CultureInfo, ByVal value As Object) As Object
If (value Is GetType(String)) Then
Dim v As String() = (CStr(value).Split(New [Char]() {" "c}))
Return New Name(v(0), v(1))
End If
Return MyBase.ConvertFrom(context, culture, value)
End Function
Public Overrides Function ConvertTo( _
ByVal context As ITypeDescriptorContext, _
ByVal culture As CultureInfo, ByVal value As Object, _
ByVal destinationType As Type) As Object
If (destinationType Is GetType(String)) Then
Return (CType(value, Name).First + " " + (CType(value, Name).Last))
End If
Return MyBase.ConvertTo(context, culture, value, destinationType)
End Function
End Class
C#
using System;
using System.ComponentModel;
using System.Globalization;
public class Name
{
private string _first;
private string _last;
public Name(string first, string last)
{
_first=first;
_last=last;
}
public string First
{
get{ return _first;}
set { _first = value;}
}
public string Last
{
get { return _last;}
set { _last = value;}
}
}
public class NameConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType) {
1257
Trang 8if (sourceType == typeof(string)) {
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value) {
if (value is string) {
string[] v = ((string)value).Split(new char[] {’ ’});
return new Name(v[0],v[1]);
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destinationType) {
if (destinationType == typeof(string)) {
return ((Name)value).First + " " + ((Name)value).Last;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
TheNameConverter class overrides three methods, CanConvertFrom, ConvertFrom, and
ConvertTo TheCanConvertFrommethod allows you to control what types the converter can convert
from TheConvertFrommethod converts the string representation back into aName object, and
ConvertToconverts theNameobject into a string representation
After you have built your type converter, you can use it to mark properties in your control with the
TypeConverterattribute, as you saw in Listing 26-35
Control Designers
Controls that live on the Visual Studio design surface depend on control designers to create the design-time
experience for the end user Control designers, for both WinForms and ASP.NET, are classes that derive
from theSystem.ComponentModel.Design.ComponentDesignerclass .NET provides an abstracted
base class specifically for creating ASP.NET control designers called theSystem.Web.UI.Design
.ControlDesigner In order to access these classes you will need to add a reference to the System
.Design.dll assembly to your project
.NET includes a number of in-box control designer classes that you can use when creating a custom
control; but as you develop server controls, you see that NET automatically applies a default designer
The designer it applies is based on the type of control you are creating For instance, when you created
your first TextBox control, Visual Studio used theControlDesignerclass to achieve the WYSIWYG
design-time rendering of the text box If you develop a server control derived from theControlContainer
class, NET automatically use theControlContainerDesignerclass as the designer
Trang 9You can also explicitly specify the designer you want to use to render your control at design time using theDesignerattribute on your control’s class, as shown in Listing 26-41
Listing 26-41: Adding a Designer attribute to a control class
VB
<DefaultProperty("Text")> _
<ToolboxData("<{0}:WebCustomControl1 runat=server></{0}:WebCustomControl1>")> _
<Designer(GetType(System.Web.UI.Design.ControlDesigner))> _
Public Class WebCustomControl1
Inherits System.Web.UI.WebControls.WebControl
C#
[DefaultProperty("Text")]
[ToolboxData("<{0}:WebCustomControl1 runat=server></{0}:WebCustomControl1>")]
[Designer(typeof(System.Web.UI.Design.ControlDesigner))]
public class WebCustomControl1 : WebControl
Notice that theDesignerattribute has been added to theWebCustomControl1class You have specified that the control should use theControlDesignerclass as its designer Other in-box designers you could have specified are
❑ CompositeControlDesigner
❑ TemplatedControlDesigner
❑ DataSourceDesigner
Each designer provides a specific design-time behavior for the control, and you can select one that is
appropriate for the type of control you are creating
Design-Time Regions
As you saw earlier, ASP.NET allows you to create server controls that consist of other server controls
and text In ASP.NET 1.0, a server control developer could use theReadWriteControlDesignerclass
to enable the user of the server control to enter text or drop other server controls into a custom server
control at design time An example of this is the ASP.NET Panel control, which enables developers to
add content to the panel at design time
Since ASP.NET 2.0, however, creating a control with this functionality has changed TheReadWrite
ControlDesignerclass was marked as obsolete, and a new and improved way was included to
allow the developer to create server controls that have design-time editable portions The new technique,
called designer regions, is an improvement over theReadWriteControlDesignerin several ways First,
unlike theReadWriteControlDesignerclass, which allowed only a single editable area, designer regions enable you to create multiple, independent regions defined within a single control Second, designer
classes can now respond to events raised by a design region This might be the designer drawing a
control on the design surface or the user clicking an area of the control or entering or exiting a template edit mode
1259
Trang 10To show how you can use designer regions, create a container control to which you can apply a custom
control designer (as shown in Listing 26-42)
Listing 26-42: Creating a composite control with designer regions
VB
<Designer(GetType(MultiRegionControlDesigner))> _
<ToolboxData("<{0}:MultiRegionControl runat=server width=100%>" & _
"</{0}:MultiRegionControl>")> _
Public Class MultiRegionControl
Inherits CompositeControl
’ Define the templates that represent 2 views on the control
Private _view1 As ITemplate
Private _view2 As ITemplate
’ These properties are inner properties
<PersistenceMode(PersistenceMode.InnerProperty), DefaultValue("")> _
Public Overridable Property View1() As ITemplate
Get Return _view1 End Get
Set(ByVal value As ITemplate) _view1 = value
End Set End Property
<PersistenceMode(PersistenceMode.InnerProperty), DefaultValue("")> _
Public Overridable Property View2() As ITemplate
Get Return _view2 End Get
Set(ByVal value As ITemplate) _view2 = value
End Set End Property
’ The current view on the control; 0= view1, 1=view2, 2=all views
Private _currentView As Int32 = 0
Public Property CurrentView() As Int32
Get Return _currentView End Get
Set(ByVal value As Int32) _currentView = value End Set
End Property
Protected Overrides Sub CreateChildControls()
MyBase.CreateChildControls()
Controls.Clear()
Dim template As ITemplate = View1