Walkthrough: Adding Smart Tags to a Windows Forms Component

Caution

This content was written for .NET Framework. If you're using .NET 6 or a later version, use this content with caution. The designer system has changed for Windows Forms and it's important that you review the Designer changes since .NET Framework article.

Smart tags are menu-like user interface (UI) elements that supply commonly used design-time options. Most of the standard components and controls provided with the .NET Framework contain smart tag and designer verb enhancements. The procedures in this walkthrough show you how to add smart-tag support to components and custom controls.

You can add smart tags to Window Forms components to supply commonly used design-time options. Items in a smart-tag panel are logically grouped by category, and individual DesignerActionMethodItem instances may optionally be duplicated as designer verb entries. Many of the standard components and controls provided with the .NET Framework contain smart tag and designer verb enhancements. Component and custom control authors can also add smart-tag support, typically by using the push model.

Adding smart tags with the push model requires the following additions to the component project:

  • Implementation of a class, derived from DesignerActionList, that defines the methods and properties that are targets of the smart-tag menu items. This class may also supply an overridden GetSortedActionItems method that returns an array of DesignerActionItem instances.

  • The designer class associated with the component must implement the ActionLists property. Retrieving this property supplies the DesignerActionListCollection that contains all the DesignerActionList instances associated with a single smart-tag menu. Often there is only one list in such a collection.

Note

Smart-tag panels do not support scrolling or paging, so be careful not to populate your panels with many smart-tag items. Too many items may result in the smart-tag panel extending beyond the screen boundary.

The following procedure demonstrates how to add smart tags using code from a simple example control, ColorLabel, which is derived from the standard Windows Forms Label control. This control has an associated designer named ColorLabelDesigner.

To copy the code in this topic as a single listing, see How to: Attach Smart Tags to a Windows Forms Component.

Prerequisites

In order to complete this walkthrough, you will need:

  • Sufficient permissions to be able to create and run Windows Forms application projects on the computer where the .NET Framework is installed.

To implement a class derived from DesignerActionList

  1. In the same namespace as your component, add the declaration for your class derived from DesignerActionList.

    Note

    You must add a reference to the design-time assembly, System.Design.dll. This assembly is not included in the .NET Framework 4 Client Profile. To add a reference to System.Design.dll, you must change the project's Target Framework to .NET Framework 4.

    Public Class ColorLabelActionList
        Inherits System.ComponentModel.Design.DesignerActionList
    
    public class ColorLabelActionList :
              System.ComponentModel.Design.DesignerActionList
    
  2. Add a constructor to this class that takes an instance of the associated control. Supply a private field to hold a reference to this instance. Also supply a private field to cache a reference to the DesignerActionService. This will be used to update the list.

    Private colLabel As ColorLabel
    
    
    ...
    
    
    Private designerActionUISvc As DesignerActionUIService = Nothing
    
    
    ...
    
    
    Public Sub New(ByVal component As IComponent)
    
        MyBase.New(component)
        Me.colLabel = component
    
        ' Cache a reference to DesignerActionUIService, so the 
        ' DesigneractionList can be refreshed. 
        Me.designerActionUISvc = _
        CType(GetService(GetType(DesignerActionUIService)), _
        DesignerActionUIService)
    
    End Sub
    
    private ColorLabel colLabel;
    
    
    ...
    
    
    private DesignerActionUIService designerActionUISvc = null;
    
    
    ...
    
    
    public ColorLabelActionList( IComponent component ) : base(component) 
    {
        this.colLabel = component as ColorLabel;
    
        // Cache a reference to DesignerActionUIService, so the 
        // DesigneractionList can be refreshed. 
        this.designerActionUISvc =
            GetService(typeof(DesignerActionUIService))
            as DesignerActionUIService;
    }
    
  3. Add methods and properties that you want to associate to smart-tag items. Methods will be executed when their corresponding smart-tag entry is selected. Properties should have getter sections so that their current value is displayed; they may optionally have setter sections that use the GetProperties method if their values are to be editable from the corresponding smart-tag entry.

    Note

    As is the case throughout the design-time environment, a property is capable of being edited only if one of the base types is provided by the .NET Framework, the type can be converted into a base type by a supplied TypeConverter, or when a custom UITypeEditor is supplied.

    Public Property ForeColor() As Color
        Get 
            Return colLabel.ForeColor
        End Get 
        Set(ByVal value As Color)
            GetPropertyByName("ForeColor").SetValue(colLabel, value)
        End Set 
    End Property
    
    
    ...
    
    
    'Boolean properties are automatically displayed with binary  
    ' UI (such as a checkbox). 
    Public Property LockColors() As Boolean 
        Get 
            Return colLabel.ColorLocked
        End Get 
        Set(ByVal value As Boolean)
            GetPropertyByName("ColorLocked").SetValue(colLabel, value)
    
            ' Refresh the list. 
            Me.designerActionUISvc.Refresh(Me.Component)
        End Set 
    End Property
    
    
    ...
    
    
    Public Sub InvertColors()
        Dim currentBackColor As Color = colLabel.BackColor
        BackColor = Color.FromArgb( _
        255 - currentBackColor.R, _
        255 - currentBackColor.G, _
        255 - currentBackColor.B)
    
        Dim currentForeColor As Color = colLabel.ForeColor
        ForeColor = Color.FromArgb( _
        255 - currentForeColor.R, _
        255 - currentForeColor.G, _
        255 - currentForeColor.B)
    End Sub
    
    public Color ForeColor
    {
        get
        {
            return colLabel.ForeColor;
        }
        set
        {
            GetPropertyByName("ForeColor").SetValue(colLabel, value);
        }
    }
    
    
    ...
    
    
    // Boolean properties are automatically displayed with binary  
    // UI (such as a checkbox). 
    public bool LockColors
    {
        get
        {
            return colLabel.ColorLocked;
        }
        set
        {
            GetPropertyByName("ColorLocked").SetValue(colLabel, value);
    
            // Refresh the list. 
            this.designerActionUISvc.Refresh(this.Component);
        }
    }
    
    
    ...
    
    
    public void InvertColors()
    {
        Color currentBackColor = colLabel.BackColor;
        BackColor = Color.FromArgb(
            255 - currentBackColor.R, 
            255 - currentBackColor.G, 
            255 - currentBackColor.B);
    
        Color currentForeColor = colLabel.ForeColor;
        ForeColor = Color.FromArgb(
            255 - currentForeColor.R, 
            255 - currentForeColor.G, 
            255 - currentForeColor.B);
    }
    
  4. Optionally implement an overridden version of the GetSortedActionItems method to return an array of DesignerActionItem instances, where each item is associated with a property or method created in the previous step. You can do this to change the order of the items, categorize them, or optionally show them. The list may also include static items, such as logical group titles.

    Public Overrides Function GetSortedActionItems() _
    As DesignerActionItemCollection
        Dim items As New DesignerActionItemCollection()
    
        'Define static section header entries.
        items.Add(New DesignerActionHeaderItem("Appearance"))
        items.Add(New DesignerActionHeaderItem("Information"))
    
        'Boolean property for locking color selections.
        items.Add(New DesignerActionPropertyItem( _
        "LockColors", _
        "Lock Colors", _
        "Appearance", _
        "Locks the color properties."))
    
        If Not LockColors Then
            items.Add( _
            New DesignerActionPropertyItem( _
            "BackColor", _
            "Back Color", _
            "Appearance", _
            "Selects the background color."))
    
            items.Add( _
            New DesignerActionPropertyItem( _
            "ForeColor", _
            "Fore Color", _
            "Appearance", _
            "Selects the foreground color."))
    
            'This next method item is also added to the context menu  
            ' (as a designer verb).
            items.Add( _
            New DesignerActionMethodItem( _
            Me, _
            "InvertColors", _
            "Invert Colors", _
            "Appearance", _
            "Inverts the fore and background colors.", _
            True))
        End If
        items.Add( _
        New DesignerActionPropertyItem( _
        "Text", _
        "Text String", _
        "Appearance", _
        "Sets the display text."))
    
        'Create entries for static Information section. 
        Dim location As New StringBuilder("Location: ")
        location.Append(colLabel.Location)
        Dim size As New StringBuilder("Size: ")
        size.Append(colLabel.Size)
    
        items.Add( _
        New DesignerActionTextItem( _
        location.ToString(), _
        "Information"))
    
        items.Add( _
        New DesignerActionTextItem( _
        size.ToString(), _
        "Information"))
    
        Return items
    End Function
    
    public override DesignerActionItemCollection GetSortedActionItems()
    {
        DesignerActionItemCollection items = new DesignerActionItemCollection();
    
        //Define static section header entries.
        items.Add(new DesignerActionHeaderItem("Appearance"));
        items.Add(new DesignerActionHeaderItem("Information"));
    
        //Boolean property for locking color selections.
        items.Add(new DesignerActionPropertyItem("LockColors",
                         "Lock Colors", "Appearance",
                         "Locks the color properties."));
        if (!LockColors)
        {
            items.Add(new DesignerActionPropertyItem("BackColor",
                             "Back Color", "Appearance",
                             "Selects the background color."));
            items.Add(new DesignerActionPropertyItem("ForeColor",
                             "Fore Color", "Appearance",
                             "Selects the foreground color."));
    
            //This next method item is also added to the context menu  
            // (as a designer verb).
            items.Add(new DesignerActionMethodItem(this,
                             "InvertColors", "Invert Colors",
                             "Appearance",
                             "Inverts the fore and background colors.",
                              true));
        }
        items.Add(new DesignerActionPropertyItem("Text",
                         "Text String", "Appearance",
                         "Sets the display text."));
    
        //Create entries for static Information section.
        StringBuilder location = new StringBuilder("Location: ");
        location.Append(colLabel.Location);
        StringBuilder size = new StringBuilder("Size: ");
        size.Append(colLabel.Size);
        items.Add(new DesignerActionTextItem(location.ToString(),
                         "Information"));
        items.Add(new DesignerActionTextItem(size.ToString(),
                         "Information"));
    
        return items;
    }
    

To update the associated designer class to implement the ActionLists property

  1. Locate the designer class for the control. If one does not exist, create a designer class and associate it to the control class. For more information on designers, see Base Designer Classes.

  2. As an optimization technique, add a private field of type DesignerActionListCollection.

    Private lists As DesignerActionListCollection
    
    private DesignerActionListCollection actionLists;
    
  3. Add the overridden ActionLists property to return a new instance of the ColorLabelActionList class that you created earlier.

    Public Overrides ReadOnly Property ActionLists() _
    As DesignerActionListCollection
        Get 
            If lists Is Nothing Then
                lists = New DesignerActionListCollection()
                lists.Add( _
                New ColorLabelActionList(Me.Component))
            End If 
            Return lists
        End Get 
    End Property
    
    public override DesignerActionListCollection ActionLists
    {
        get
        {
            if (null == actionLists)
            {
                actionLists = new DesignerActionListCollection();
                actionLists.Add(
                    new ColorLabelActionList(this.Component));
            }
            return actionLists;
        }
    }
    

Comments

Several areas of the code deserve a more detailed explanation:

  • When a property or method in the class derived from DesignerActionList changes the state of the associated control, these changes should not be made by direct setter calls to the component's properties. Instead, such changes should be made through an appropriately created PropertyDescriptor. This indirect approach ensures that smart-tag undo and UI update actions function correctly.

  • You can dynamically update the smart-tag panel by calling DesignerActionUIServiceRefresh. This process can be used to dynamically change the contents of the smart-tag panel. In the example, the smart tags concerned with changing colors are conditionally included depending on the state of the LockColors property. This Boolean property is also associated with a smart tag, so the developer can lock or unlock the current color selection, at least through the menu.

  • A smart-tag entry of type DesignerActionMethodItem can be optionally included in the shortcut menu for the associated control by setting the includeAsDesignerVerb parameter in the constructor to true. The .NET Framework then implicitly creates a corresponding DesignerVerb and adds it to the shortcut menu for you. In this example, the InvertColors item is treated in this manner.

  • Smart-tag items are grouped in a panel by their Category property, which is set in the constructor for each item. If this property is not explicitly set, it is assigned to the default category. Each item is ordered in the smart-tag panel by category, then by order of occurrence in the DesignerActionItem array returned by the class derived from DesignerActionList class. This example contains two categories: Appearance and Information.

    Note

    No DesignerActionHeaderItem is supplied for the second category.

  • An entry that displays static text information can be implemented using either a DesignerActionTextItem or a DesignerActionPropertyItem whose associated property only contains a setter. This example uses the former approach.

Next Steps

Once you have started integrating your component into the design-time environment, consider expanding its designer support.

See Also

Reference

DesignerVerb

DesignerActionItem

DesignerActionList

ActionLists

DesignerActionService

Concepts

Designer Commands and the DesignerAction Object Model for Windows Forms