DSL Tools #6 - Custom Property Editors

Well, just a quick return to this DSL Tools how to's to reference here a feature that is commonly required and that's not so easy to implement.

The scenario is the following. You have a given domain property - let's say "Dependency Entity" - that should have a dynamic list of possible values - this means that the values depend on the current state of the model (the other domain classes already in place).

To illustrate this concept, I've prepared the following meta-model:

Click to see full size image

This meta-model allows the user to add multiple entities to a graph. Each entity has 2 properties: Name and DependencyEntity.

For this last property I want the user to be able to select from a combo any of the other entities placed in the designer. Something like this:

Click to see full size image

This can be done by implementing what I would call a "custom property editor".

1. Create the custom UITypeEditor

Create a new class named DependencyEntityTypeEditor and have it inherit from System.Drawing.Design.UITypeEditor.

You will need to override 2 methods:

  • GetEditStyle - to inform the framework that this editor will use the DropDown style.
  • EditValue - to perform the actual feature, by creating and filling a list box with the possible values that you want the property to have.

This code is pretty straightforward. Notice that I iterate through all the entities present in the graph to fill the list box.

using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Globalization;
using System.Windows.Forms.Design;

namespace MyModel.EntitiesModel
{
   
/// <summary>
   
/// Implements the DependencyEntity domain attribute property
    /// editor.
   
/// </summary>
   
public class DependencyEntityTypeEditor : UITypeEditor
   
{
       
#region Members
        private IWindowsFormsEditorService formsEditorService;
       
#endregion

            #region Public Methods
        /// <summary>
        /// Gets the editor style used by the EditValue method.
        /// </summary>
       
/// <param name="context">Additional context information.
        /// </param>

        /// <returns>
       
/// A value that indicates the style of editor used by the
        /// EditValue method.

       
/// If the UITypeEditor does not support this method, then
        ///
GetEditStyle will return None.
        /// </returns>
        public override UITypeEditorEditStyle GetEditStyle
            (ITypeDescriptorContext context)
        {
            if (context != null)
            { return UITypeEditorEditStyle.DropDown;
            }
            return base.GetEditStyle(context);
        }

        /// <summary>
       
/// Edits the specified object's value using the editor style
        /// indicated by the GetEditStyle method.

        /// </summary>
       
/// <param name="context">Additional context information.</param>
       
/// <param name="provider">A provider that this editor can use to
        /// obtain services.</param>

        /// <param name="value">The object to edit.</param>
        /// <returns>
       
/// The new value of the object. If the value of the object has not
        /// changed, this should return the
same object it was passed.
       
/// </returns>
        public override object EditValue(ITypeDescriptorContext context,
            IServiceProvider provider, object value)
        {
            // Default behavior
            if ((context == null) ||
                (provider == null) || (context.Instance == null))
            {
                return base.EditValue(context, provider, value);
            } // Get current entity
            EntityShape entityShape = (context.Instance as EntityShape);
            if (entityShape != null)
            {
                Entity entity = (entityShape.ModelElement as Entity);
                if (entity != null)
                {
                    // Current entity name
                    string entityName = entity.Name;
                    // Current graph
                    EntitiesGraph graph = entity.Graph;
                    if (graph != null)
                    {
                        // Build list box
                        ListBox listBox = new ListBox();
                        listBox.Sorted = true;
                        listBox.Click += new EventHandler(listBox_Click);
                        listBox.BorderStyle = BorderStyle.None;

                                    foreach (Entity childEntity in graph.Entities)
                        {
                           
if (string.Compare(childEntity.Name,
                                entityName, true,
                                CultureInfo.CurrentUICulture) != 0)
                            {
                                listBox.Items.Add(childEntity.Name);
                            }
                        }

                                    listBox.SelectedItem = value;

                                    // Handle the service
                        this.formsEditorService =
                            (IWindowsFormsEditorService)provider.
                            GetService(typeof(IWindowsFormsEditorService));
                        this.formsEditorService.DropDownControl(listBox);

                                    // Result
                        return listBox.SelectedItem;
                    }
                }
            }

                  // Default behavior
            return base.EditValue(context, provider, value);
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Handles the Click event of the listBox control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
       
/// <param name="e">The <see cref="System.EventArgs"/> instance
        /// containing the event data.</param>

        private void listBox_Click(object sender, EventArgs e)
        {
            if (this.formsEditorService != null)
            {
                this.formsEditorService.CloseDropDown();
            }
        }
       
#endregion
    }
}

2. Create a new custom attribute for DependencyEntity domain property

Back in the DslDefinition designer, select the DependencyEntity property and, under its properties, click the ellipsis next to Custom Attributes.

You'll need to add the following values:

  • Name: System.ComponentModel.Editor
  • Parameter: typeof(MyModel.EntitiesModel.DependencyEntityTypeEditor), typeof(System.Drawing.Design.UITypeEditor)

Click to see full size image

Notice that MyModel.EntitiesModel.DependencyEntityTypeEditor matches the full name of the class created in the previous step.

3. Transform all templates

Before this extension starts working you will need to transform all text templates in your DSL solution. After this step the customization is setup and you'll have the behavior described.

This is simpler than you could imagine from the requirements. :)

Published 04 October 07 04:27 by hgr
Filed under: ,

Comments

# Joao Gouveia said on October 31, 2007 2:37 PM:

I was looking for something like this, but  to provide filtered values in the editor list for a role attribute, not for a single property.

I did not find any reference to that but I used the some strategy and I got it working:

1. Define the ustom attribute for the role (not the domain property)

2. Add similar code, but put the role target valid objects in the list (make shure they have a nice ToString() do display or wrapped them in some object for that purpose)

3. Return the ModelElement.Id of the selected object in the EditValue method (this is the major difference)

and that´s it... you have only available in the list the objects that are possible for that role!

# Hugo Ribeiro said on December 20, 2007 6:38 PM:

In this previous post I described how to create a custom property editor to show a list box that the

# Hugo Ribeiro said on July 15, 2008 6:43 PM:

Since I've been doing a number of posts on the DSL Tools, I thought it would be helpful to have a list

Anonymous comments are disabled