Practical ASP.NET
Roll Your Own Control Designers
It's not enough to build a great custom control to help your end users -- you must also help other developers use your control.
Technology Toolbox: Visual Basic, ASP.NET, XML
Your custom Web control has two audiences: the end user who interacts with the page that the control appears on, and the developer who uses your control to create that page.
p>Great custom controls support both audiences, making end users
and developers more productive. Your first priority should always be to help end users accomplish their goals, but your second priority should always be to help the developer who is using your control do so more easily and with less effort. This means you should create a control designer that simplifies the development activities for your control after you provide the methods, properties, and events that make your control useful to that developer.
One way to support the developer using your custom control in Visual Studio 2005 is to give your control a SmartTag. A SmartTag supports developers in two ways. It provides quick access to frequently used properties, and it creates single-click solutions for common scenarios. I'll walk you through how to accomplish both of these tasks. The good news? Adding a SmartTag to your custom control isn't difficult. The key to creating a great SmartTag is to understand how developers will use your custom control (see Figure 1).
You must first create a control designer to add a SmartTag to your custom control. The easiest way to add the control designer to your project is to add a Web Custom Control, and then have the control inherit from the System.Web.UI.Design.ControlDesigner class in System.Design DLL. The System.Design DLL isn't one of the default references added to a project by the Visual Studio 2005 templates, so you need to add a reference to this DLL to your project.
You should remove the default code in the class module after you've changed the Inherits line because you won't need it. Creating the start point for your designer is easy:
Public Class ADesigner
Inherits System.Web.UI. _
Design.ControlDesigner
End Class
Once you create the control designer, you add a SmartTag to the class by overriding the designer's ActionLists property. A SmartTag consists of a list of items that you assemble in the ActionLists property from one or more DesignerActionList classes. Each DesignerActionList provides one or more items for the SmartTag. This architecture lets you create multiple DesignerActionList classes independent of any control, and then combine them as needed for any particular control. In the article's sample, you create a DesignerActionList that lets developers set a control's Text property (TextActionList) that you use with a variety of custom controls. You also create a second DesignerActionList that you tailor to work with a single custom control (FormatActionList) to simplify setting groups of formatting-related properties.
Create the ActionListCollection
Visual Studio might call the ActionLists property multiple times, so you don't want to recreate the ActionListCollection each time. Instead, declare the collection at the top of your class and, in the ActionLists property, create the collection only if it isn't already present (see Listing 1).
Next, define the two DesignerActionLists used in the ActionLists property. It's easier to start with the TextActionList, which allows the developer to set a commonly used property (the Text property) without resorting to using the Property window.
You must also define the DesignerActionList constructor that accepts the reference to the custom control; it's passed in the ActionLists property when the DesignerActionList is created. In the ActionLists property, the DesignerActionList is created, then passed a reference to the control being designed. So, the DesignerActionList constructor must accept that reference. You should also set a class-level variable to the reference so that other routines in the DesignerActionList will have access to the control being designed.
For the TextActionList, you define the reference passed to the constructor as WebControl, which allows this DesignerActionList to work with a wide variety of WebControls. You might also include code to provide additional tests that check whether the correct control type is being passed. For example, assume you want to support many, but not all, WebControls. You can test for the controls that you want to support in this method. You can also add an Imports statement to simplify the code in the DesignerActionList:
Imports System.ComponentModel.Design
Public Class TextActionList
Inherits DesignerActionList
Private MyCtl As WebControl
Public Sub New(ByVal ctl As WebControl)
MyBase.New(ctl)
MyCtl = ctl
End Sub
Your next step is to override the DesignerActionList's GetSortedActionItems method, which returns a collection of action items. You add to this collection the items that you want to appear in the SmartTag when you assemble the SmartTag in the ActionLists property.
You can add four different kinds of items to the collection: methods, which appear as links on the SmartTag; properties, which appear as editable entries; headers, which are formatted text; and text, which consists of explanatory plain text that is inserted after headers. Your code in the GetSortedActionItems method must assemble these items into a DesignerActionItemCollection and return the collection to the ActionLists property.
This code prepares your collection for adding necessary items to it:
Public Overrides Function GetSortedActionItems() _
As DesignerActionItemCollection
Dim itms As DesignerActionItemCollection
Dim hdr As DesignerActionHeaderItem
Dim txt As DesignerActionTextItem
Dim prop As DesignerActionPropertyItem
itms = New DesignerActionItemCollection
Create SmartTag Items
Next, you need to create the items themselves. For the TextActionList, put a header, a text item, and a property item into the collection. The header item is the easiest to create because you need to pass only the text you want to display:
hdr = New DesignerActionHeaderItem( _
"Set Display Properties")
Text items are only slightly more complicated. When you create a Text item, you must pass the text you want to display, as well as a string that specifies the category for the item. Visual Studio will display all items in the same category together when Visual Studio shows your SmartTag. Visual Studio separates items in the same category from different ActionLists with horizontal lines. You can also specify a category for your heading items.
This example adds some explanatory text to the SmartTag and specifies that the given item is in the PropertySettings category:
txt = New DesignerActionTextItem( _
"Set the text to be displayed when the page" & _
"is loaded.", "PropertySettings")
The last task for the TextActionList is to create a Property item for the SmartTag that lets a developer change properties on the control. You must specify four parameters when you create a DesignerActionProperty: a property within your DesignerActionList class, a label to display beside the property, a category, and, optionally, a description. (The description appears as a ToolTip in Visual Studio when a developer hovers his or her mouse over your label.)
This example creates a DesignerActionPropertyItem tied to a property called InitialText in the class. This property item is a member of the PropertySettings category; it also includes a ToolTip:
prop = New DesignerActionPropertyItem( _
"InitialText", "Text:", "PropertySettings", _
"Text to be displayed when the " &
"control is loaded")
Finish up by adding each item to the collection and returning the collection:
itms.Add(hdr)
itms.Add(txt)
itms.Add(prop)
Return itms
You're now ready to create a designer property by defining any properties that you specified when creating DesignerActionPropertyItems. In this case, that is a property called InitialText. Visual Studio passes this property the value the developer sets in the SmartTag. Your code typically uses this value to update the custom control the developer is designing.
In the InitialText property, you access the custom control through the class-level variable that you set in the ActionList's constructor. You declared the class-level variable as WebControl, so the Text property for the custom control doesn't appear in the IntelliSense drop lists. However, you can use the GetProperty method to retrieve the Text property, and either set it or return it in the property:
Public Property InitialText()
Get
Return MyCtl.GetType.GetProperty(_
"Text"). GetValue(MyCtl, Nothing)
End Get
Set(ByVal value)
MyCtl.GetType.GetProperty("Text"). _
SetValue(MyCtl, value, Nothing)
End Set
End Property
The smart thing to do at this point is to make sure that you're building the SmartTag lists and items correctly. You must tie your designer class to your custom control for Visual Studio to display your SmartTag. Do this by adding a Designer attribute to your control's Class definition and passing it the designer's type. This example ties the designer (ADesigner) in the project (ControlDesignerDemo) to a control called AControlForADesigner (see Figure 2):
< Designer(GetType(ControlDesignerDemo.ADesigner)), _
ToolboxData("<{0}:AControlForADesigner _
runat=server></{0}:AControlForADesigner>")> _
Public Class AControlForADesigner
Inherits WebControl
End Class
Add the Desired Methods
SmartTags can do more than provide quick access to common properties. You can also use them to provide simple solutions to typical scenarios. For example, a common task when using controls is to set the multiple formatting properties that are available to the control. You can make life easier for a developer who uses your control by providing a dialog that allows the developer to set several formatting properties with a single click.
You implement this kind of functionality by adding methods to your SmartTag, some of which open dialog boxes for the developer to interact with. The FormatActionList class adds two methods to the list that appear on the SmartTag as hyperlinks. The first method, ResetFormat, is the simplest; this method sets several properties on the custom control to their default state when the developer selects this method. The second method, SetFormat, opens a dialog that allows the developer to select between two predefined styles. The code behind this method sets several properties on the custom control, based on the developer's choices.
You must pass the DesignerActionMethodItem method five items when you create a method item: a reference to the DesignerActionList, the name of the method you want to call, a label you use in the hyperlink on the control, a category name, and a description. This example defines two method items, then adds the items to the DesignerActionItemCollection before returning the collection (see Listing 2).
The ResetFormat method arbitrarily sets some formatting properties to default values. You pass the FormatActionList's constructor a reference to the AControlForADesigner class, which enables you to access the properties of the control directly, including the Text property:
Public Sub ResetFormat()
MyCtl.Text = ""
MyCtl.BackColor = Drawing.Color.White
MyCtl.ForeColor = Drawing.Color.Black
' ...more properties...
End Sub
The SetFormat method opens a dialog that allows the user to select any of several options. After the user closes the dialog, the code checks properties on the dialog and uses them to set properties on the custom control (see Figure 3):
Public Sub SetFormat()
Dim frm As FormatStyles
Dim dlr As System.Windows.Forms.DialogResult
frm = New FormatStyles
frm.ShowDialog
If frm.BandW = True Then
MyCtl.BackColor = Drawing.Color.White
MyCtl.ForeColor = Drawing.Color.Black
' ...more properties...
ElseIf frm.Color = True Then
MyCtl.BackColor = Drawing.Color.Red
MyCtl.ForeColor = Drawing.Color.Green
...more properties...
ElseIf
ResetFormat
End If
End Sub
Once you know the common scenarios for using your control, you can add support for them to your SmartTag. Your goal isn't to recreate Visual Studio's Property window on the control, but to simplify the developer's life. A great SmartTag should provide access to the most commonly used properties, but avoid filling up theItems list with properties the developer won't take advantage of frequently. The same is true of the methods that you add to your SmartTag: Methods that set multiple properties must not create more work for the developer by setting properties that the developer must back out later.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.