RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Gain Design-Time Power with Control Editors, Part 2 of 2 : Page 2

Learn how to add custom editing dialogs, smart tags, and property pages to your controls.

Building Smart Tags and Command Verbs
If you select one or more controls in the form designer in Visual Studio 2005, a little box with an arrow appears near the control's upper-right corner (see Figure 2). If you click this arrow, a smart tag popup appears. This popup contains the properties that you are most likely to want to modify for the selected control(s). If you select one control, or a group of controls of the same type, the popup can also include information and links that invoke command verbs to perform some action on the control. Clicking the little box again or clicking anywhere outside of the popup closes the smart tag popup.

Figure 2 shows the smart tag popup for the StateSelector control, which lets you set the control's LineStyle and Map properties. The smart tag also displays an information section indicating that this map includes 51 states. At the bottom of the smart tag, command verbs let you reset the map, and select, deselect, or toggle every state.

Figure 2. Custom Smart Tag: Creating smart tags lets developers set a control's common properties.
To build a smart tag for a control, start by adding the Designer attribute to the control's class. Here's the Designer attribute for the StateSelector class indicating that the StateSelectorDesigner class is the smart tag designer for the StateSelector control:

   <Designer(GetType(StateSelectorDesigner))> _
   Public Class StateSelector
   End Class
The following code shows the StateSelectorDesigner class:

   Public Class StateSelectorDesigner
      Inherits ControlDesigner
      Private m_Lists As DesignerActionListCollection
      Public Overrides ReadOnly Property ActionLists() _
         As System.ComponentModel. _
            If m_Lists Is Nothing Then
               m_Lists = New DesignerActionListCollection
               m_Lists.Add( _
                  New StateSelectorActionList(Me.Component))
            End If
            Return m_Lists
         End Get
      End Property
   End Class
The StateSelectorDesigner class inherits from the ControlDesigner class and then overrides its ActionLists method to perform its only real task: returning a list of the "actions" that should be listed on the smart tag popup. The ActionLists code creates a DesignerActionListCollection object and adds a new StateSelectorActionList to it.

The StateSelectorActionList class defines the smart tag's contents. To save space, only some of the code is shown here. Download the example project to see the complete code.

The following code declares private variables to hold references to the StateSelector control that it is editing and to a designer service that it can use to refresh the smart tag popup. The class's constructor sets these references:

   ' The StateSelector we are designing.
   Private m_StateSelector As StateSelector
   Private m_DesignerService As DesignerActionUIService = Nothing
   Public Sub New(ByVal component As IComponent)
      ' Save a reference to the control we are designing.
      m_StateSelector = DirectCast(component, StateSelector)
      ' Save a reference to the DesignerActionUIService
      ' so we can refresh it later.
      m_DesignerService = _
         CType(GetService(GetType(DesignerActionUIService)), _
   End Sub
Next, for each property that the smart tag popup should display, the class includes a property. The code includes a method for each command verb that it should display. Attributes tell the smart tag how the item should be used.

The following code shows the Map property. Notice that the code uses the same TypeConverter and Editor attributes used by the control's Map property and described in the first part of this article. This allows the smart tag to reuse the type converter and editor so it can manage the Map property the same way the Properties window does, displaying the value as (none) or (map) and providing a file selection dialog to edit the Map value:

   <TypeConverter(GetType(StatesMapConverter))> _
   <Editor(GetType(StatesMapEditor), GetType(UITypeEditor))> _
   <DefaultValue(GetType(StatesMap), Nothing)> _
   <RefreshProperties(RefreshProperties.All)> _
   Public Property Map() As StatesMap
         Return m_StateSelector.Map
      End Get
      Set(ByVal value As StatesMap)
         SetControlProperty("Map", value)
         ' Refresh the designer service to show
         ' the new number of states.
      End Set
   End Property
The Property Set procedure is worth mentioning here, too. The code calls the helper method SetControlProperty to actually set the Map property's value. The code could simply set the control's Map property to the new value, but then the smart tag wouldn't know that the property has changed—and that leads to three problems:

  1. The smart tag doesn't always update the control on the form, so you don't see the change.
  2. The smart tag doesn't tell the IDE that the property has changed, which means the IDE's undo and redo commands don't handle the change properly.
  3. The IDE doesn't flag the form as modified, so if you don't make any other changes, it will allow you to close the form without saving the change.
The following code shows how the SetControlProperty method updates a property while keeping the smart tag in the picture. The code uses the TypeDescriptor class to get information about the property and set its value. The SetControlProperty method keeps both the smart tag and the IDE up to date:

   Private Sub SetControlProperty( _
    ByVal property_name As String, ByVal value As Object)
      TypeDescriptor.GetProperties(m_StateSelector) _
         (property_name).SetValue(m_StateSelector, value)
   End Sub
The smart tag action list's DoResetMap method simply sets the StateSelector control's Map property to Nothing. But it uses the SetControlProperty method to set the new property value to avoid the problems described earlier:

   ' Remove the map data.
   Public Sub DoResetMap()
      SetControlProperty("Map", Nothing)
   End Sub
The DoSelectAll method calls the control's SelectAll method to select all the states. It then sets the Map property to its current value by calling the SetControlProperty method as shown below:

   ' Select all map states.
   Public Sub DoSelectAll()
      SetControlProperty("Map", m_StateSelector.Map)
   End Sub
The last interesting part of the StateSelectorActionList class is its GetSortedActionItems method. This function both returns a list describing the items that appear on the smart tag popup, and connects the items to the class's properties and methods:

   ' Return the smart tag action items.
   Public Overrides Function GetSortedActionItems() _
    As System.ComponentModel.Design.DesignerActionItemCollection
      Dim items As New DesignerActionItemCollection()
      ' Section headers.
      items.Add(New DesignerActionHeaderItem("Appearance"))
      If m_StateSelector.Map IsNot Nothing Then
         items.Add(New DesignerActionHeaderItem("Information"))
         Dim txt As String = "# States: " & _
         items.Add( _
            New DesignerActionTextItem( _
               txt, "Information"))
      End If
      ' Property entries.
      items.Add( _
         New DesignerActionPropertyItem( _
            "LineStyle", "Line Style", _
            "Appearance", _
            "The line style used to draw state borders"))
      items.Add( _
         New DesignerActionPropertyItem( _
            "Map", "Map", "Appearance", _
            "The map data"))
      ' Method entries.
      items.Add( _
         New DesignerActionMethodItem( _
            Me, "DoResetMap", "Reset Map", _
            "Data", "Remove the map data", True))
      items.Add( _
         New DesignerActionMethodItem( _
            Me, "DoSelectAll", "Select All", _
            "Data", "Select all states", True))
      items.Add( _
         New DesignerActionMethodItem( _
            Me, "DoDeselectAll", "Deselect All", _
            "Data", "Deselect all states", True))
      items.Add( _
         New DesignerActionMethodItem( _
            Me, "DoToggleAll", "Toggle All", _
            "Data", "Toggle all states", True))
Figure 3. Free Property Window Command Verbs: Command verbs you define for smart tags (Reset Map, Select All, etc.) appear automatically at the bottom of the Properties window.
Return items End Function
The function begins by creating the DesignerActionItemCollection that it will return. It adds a DesignerActionHeaderItem to display the "Appearance" header. If the StateSelector has a map loaded, it also adds an "Information" header and a text item that displays the number of states on the map.

Next the code adds DesignerActionPropertyItems to describe the properties that the smart tag popup should display. These include the LineStyle and Map properties.

The function finishes by adding DesignerActionMethodItems to represent the command verbs at the bottom of the popup.

That's it for the smart tag, except for one bonus feature—which you get for free. When you create the smart tag code, notice that it also adds the list of command verbs at the bottom of the Properties window (see Figure 3).

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date