Adding Smart Tags support to custom User Symbol

In the previous “Diagrammer Tips and Tricks” series (see Part 1 and Part 2), you have learnt how to create a user symbol and how to define design-time capabilities. In this new article, we will explain how to enable Smart Tags on your custom User Symbol.

Note: this article requires the ILOG Diagrammer for .NET Patch 14 available on the ILOG Diagrammer for .NET support site.

What are Smart Tags

In short, Smart Tags are shortcuts for PropertySheet items accessible from the Visual Studio Design surface and specific to the current selected item. They are natively supported in the Visual Studio WinForms designer through the little arrow icon located at the top-right corner of the current selected object. Clicking on this icon pops up a menu (the Smart Tags panel) populated with items (the Smart Tags items). These items expose a reduced set of design-time configuration entries like properties or verbs (a verb describes a specific action to perform accessible from the PropertySheet). The following image shows the ListBox Smart Tags panel :

winformssmarttag.png

In Visual Studio, Smart Tags are exposed by means of DesignerActionItem instances. A DesignerActionItem defines the action to perform when the item is activated. It can be a simple property setter like what the Property Sheet provides, or a more advanced action handled through a method invocation. DesignerActionItem’s are accessible from the component’s ComponentDesigner through its DesignerActionList. When a component is selected, the Design environment asks the component designer for its DesignerActionList and populates the Smart Tags panel with the DesignerActionItem’s.

Smart Tags in ILOG Diagrammer for .NET

ILOG Diagrammer for .NET extends the Smart Tags support to GraphicObject instances, which means that you can define Smart Tags the same way you do in a WinForms custom control. For example, the following pictures shows the Smart Tags items of a ClosedCurve :

gosmarttags.png

To illustrate the article, we will use the UMLType user symbol described in the “Diagrammer Tips and Tricks” series and the following VS2008 Project.

As for a WinForms control, adding support for Smart Tags to an ILOG Diagrammer for .NET UserSymbol first requires a ComponentDesigner associated with the UserSymbol. For GraphicObject instances, the ILOG Diagrammer for .NET framework provides the GraphicObjectDesigner class that provides a set of services for GraphicObject design-time support. It is the base class we inherit from to implement our UMLType designer.

We then need to define the action items we want to add to the Smart Tag panel. For the UMLType user symbol, the TypeName, Attributes and Operations properties are good candidates. To show the different kind of possible action items, the TypeName property (which is of type String) will be exposed in the Smart Tags panel as a standard property in the same way it is exposed in a Property Sheet (that is as a description-textbox pair). The two others (which are specialized Collection) will be exposed as Verbs, which on the UI side are similar to hyperlinks that perform some actions (in our case, it pops up the Collection editor).

In term of implementation, it translates into the following steps :

  • subclass DesignerActionList
  • override the GetSortedActionItems() method to populate the list with the action items.
  • handles the actions corresponding to the action items.
  • Subclass GraphicObjectDesigner
  • override the CreateActionList method to return an instance of the DesignerActionList subclass.

Implementing the DesignerActionList

The purpose of a DesignerActionList is to provide a list of Smart Tags items to the user symbol designer and to handle the action defined by the items. Populating the list of Smart Tags items is the purpose of the DesignerActionList.GetSortedActionItems() method which should be overriden to return the items that apply to the component. Let’s start with the TypeName property we want to expose since it is the simplest Smart Tags item type.

A property Smart Tags item is defined by the DesignerActionPropertyItem class in the System.ComponentModel.Design namespace. This class easily allows to expose a property in the Smart Tags panel so that it can be edited in-place. The constructor signature is defined as:

public DesignerActionPropertyItem(
    string memberName,
    string displayName,
    string category,
    string description)

The most important parameter is the memberName argument, which refers to the associated property of the DesignerActionList subclass. It means that the UMLType TypeName property must be duplicated on the DesignerActionList as a facade to the underlying UMLType component. Here is a first draft of the UMLTypeActionList implementation:

    class UMLTypeActionList : DesignerActionList {
        public UMLTypeActionList(IComponent component)
            : base(component) {
        }          

        public string TypeName {
            get { return ((UMLType)Component).TypeName; }
            set { UMLTypeDesigner.SetProperty(GetService(typeof(IDesignerHost)) as IDesignerHost,
                                              Component,
                                              value,
                                              "TypeName",
                                              "The name of the UML type.");
            }
        }          

        public override DesignerActionItemCollection GetSortedActionItems() {
            DesignerActionItemCollection items = new DesignerActionItemCollection();
            // The "TypeName" property
            items.Add(new DesignerActionPropertyItem("TypeName",
                                                     "Type name",
                                                     "UML",
                                                     "The name of the UML type."));
            return items;
        }
    }

In the code above, the TypeName property setter invokes the UMLTypeDesigner.SetProperty method, which is a convenient method allowing to perform an undoable property setting.

In order to be able to test our action list implementation, we need to implement the UMLType designer and associate it with the UMLType component so that the Design-time API supports it. As mentioned above, the UMLType designer inherits from the GraphicObjectDesigner class and in this simple use case, the only method we need to override is the CreateActionList() factory method that returns an instance of the UMLTypeActionList class. Here is the implementation:

    public class UMLTypeDesigner : GraphicObjectDesigner {
        protected override System.ComponentModel.Design.DesignerActionList CreateActionList() {
            return new UMLTypeActionList(this.Component);
        }
    }

Finally, the last step is to associate this designer class to the UMLType class by means of the DesignerAttribute class attribute:

    [Designer(typeof(UMLTypeDesigner), typeof(IDesigner))]
    public partial class UMLType : UserSymbol
    {
      ...
    }

Running the sample at this stage gives the following result (after having added our assembly to the Visual Studio Toolbox while in diagram editing mode) :

 Smart Tags - Step 1

Ok. But what about use cases where you need more than just editing a property value ? For example, you may need to define complex actions like defining a data binding-like wizard-driven process that can be started from the Smart Tags panel. To address these use cases, the design-time API provides the DesignerActionMethodItem class which instead of relying on a property invokes a specified method that will handle the action. This is what we are going to use for the Operations and Attributes UMLType members. Instead of being exposed as a Property action item, they will be displayed as an “HyperLink”. When clicked, a modal dialog displaying the associated UITypeEditor (here the standard collection editor) will be shown.

Invoking the UITypeEditor from a Smart Tags item.

In the WinForms design-time API documentation, a UITypeEditor is defined as an editor “that can provide a user interface for representing and editing the values of objects of the supported data types”. In the sample we are writing, we will use the UITypeEditor associated with the Operations and Attributes properties and invoke its EditValue() method to edit the object’s value. Typical UITypeEditor implementations are meant to work with a PropertyGrid component, and as such are (in most cases) expecting an IWindowsFormsEditorService, as explains the Microsoft documentation (”This service is typically used to display a form from the EditValue method of a UITypeEditor”). Since a IWindowsFormsEditorService instance is available only from the PropertyGrid control, we have to write our own implementation. Let’s name it EditorService, and for convenient reasons, it will also implements the ITypeDescriptorContext and the ServiceProvider interfaces since the UITypeEditor.EditValue method expects such services.

As previously mentioned, the corresponding Attributes or Operations DesignerActionMethodItem’s should display a modal dialog holding the associated UITypeEditor (the CollectionEditor in this case). For this purpose, our IWindowsFormsEditorService implementation (the EditorService class) must implement the ShowDialog(Form dialog) method to basically invoke the Form.ShowDialog method on the dialog parameter. This method allows to specify the dialog owner via the owner’s Win32 HWND. In our case, the dialog owner to specify is simply the Smart Tags window that is exposed in the ILOG Diagrammer for .NET library by means of the ILOG.Diagrammer.Design.IDropDownOwner interface (introduced in patch 14). This interface is implemented by the DesignerHost RootComponent’s designer and represents a possible candidate for dialog ownership.

While it may appear quite complex, the following piece of code will clear up everything:

        DialogResult IWindowsFormsEditorService.ShowDialog(Form dialog) {
            IDesignerHost host =
              (IDesignerHost)((IServiceProvider)this).GetService(typeof(IDesignerHost));
            IDropDownOwner rootDesigner = null;
            if (host != null)
                rootDesigner = host.GetDesigner(host.RootComponent) as IDropDownOwner;
            if (rootDesigner == null) {
                IUIService srv =
                  (IUIService)((IServiceProvider)this).GetService(typeof(IUIService));
                if (srv != null)
                    return srv.ShowDialog(dialog);
            }
            if (rootDesigner != null) {
                IWin32Window owner = rootDesigner.DropDownOwner;
                if (owner != null)
                    return dialog.ShowDialog(owner);
            }
            return dialog.ShowDialog(_component as IWin32Window);
        }

The implementation first retrieves the IDesignerHost, then tries to get the RootComponent’s designer as a IDropDownOwner. Then, if successful, it invokes dialog.ShowDialog() method passing the Smart Tags window HWND as the dialog owner (the return value of rootDesigner.DropDownOwner).

So, with this EditorService class, we now have everything we need to write the Attributes and Operations DesignerActionMethodItem instances.

The DesignerActionMethodItem constructor is defined as:

public DesignerActionMethodItem(
    DesignerActionList actionList,
    string memberName,
    string displayName,
    string category,
    string description,
    bool includeAsDesignerVerb
)

where memberName specifies the method to invoke on the DesignerActionList instance.

The following code shows the implementation corresponding to the Attributes property (you can refer to the full source code for the Operations property and the complete EditorService implementation).

    class UMLTypeActionList : DesignerActionList {
        ...
        public void EditAttributes() {
            EditorService.EditValue(Component.Site, Component, "Attributes");
        }
        public override DesignerActionItemCollection GetSortedActionItems() {
            DesignerActionItemCollection items =
                new DesignerActionItemCollection();
            // The "TypeName" property
            ...
            // The 'Edit Attributes...' verb
            items.Add(new DesignerActionMethodItem(this,
                                    "EditAttributes",
                                    "Edit Attributes...",
                                    "UML",
                                    "The attributes of the UML type.",
                                    true));
        }
    }

And here is the final result:

Smart Tags part 2

In this article, we have seen how to support Smart Tags from a custom ILOG Diagrammer for .NET User Symbol. Using the same design-time API as the Windows Forms controls, it integrates seamlessly with the Visual Studio design surface and greatly improves the user experience of your symbols.

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • Netvouz
  • DZone
  • ThisNext
  • MisterWong
  • Wists

2 Responses to “Adding Smart Tags support to custom User Symbol”

  1. Paul Says:

    Hi Patrick,

    Is there away to associate the smart tag to the graphic object at runtime?
    So a basic2shape could have a smart tag to let a user know there is info available on that shape when clicking the tag, a custom winform etc can be displayed.

    (I realise smart tags are really for design time, but would be very useful)

    Paul.

  2. Patrick Ruzand Says:

    Hi Paul,

    You are right that Smart Tags are really meant to be used at design time.
    In order to have them at runtime, a solution is to use an ILOG.Diagrammer.Design.DiagramDesignSurface instance (in the ILOG.Diagrammer.Design assembly), which is a custom control that implements a ComponentModel. It embeds every design time features (objects have an associated Site) including the SmartTags. Let me know if it answers your question.

    Hope this helps,
    Patrick

Leave a Reply