TFS 2010 – Custom Process Parameters Part 3 – Custom Editors

In the last post of this series, we added metadata to our custom process parameter and I described all of the metadata that you can use to describe your custom process parameters. I also mentioned that we would talk more about some of the more complicated bits of metadata. The Editor property of the process parameter metadata is what I will explain in this post.

So, what is a custom editor? If you create a build definition and go to the process tab, you will see a property grid similar to the property grids that you see in Visual Studio designers. And like the property grid for the WinForms designer, this property grid supports custom editors for the properties. Take a look at the custom editor for the Build Number Format property (this property is in the Basic category). When you select the Build Number Format property a … button shows up in the value side. Clicking the button opens a dialog that is the custom editor for this property. It allows you to have a better editing experience than simply typing in the text. Of course, the property is just a string property, so typing in the text (or copying and pasting it) works, too.

Let’s continue our work from part 2 and add a custom editor for our custom process parameter.

First, I created a simple version number editor dialog:

image

Here’s the code behind:

 using System;
using System.Windows.Forms;

namespace CustomWorkflowTypes
{
    public partial class SimpleVersionDialog : Form
    {
        public SimpleVersionDialog()
        {
            InitializeComponent();
        }

        public String Version
        {
            get
            {
                return String.Format("{0}.{1}.{2}.{3}", 
                    textMajor.Text, textMinor.Text, textDate.Text, textRevision.Text);
            }
            set
            {
                String[] parts = String.IsNullOrEmpty(value) ? new String[0] : value.Split('.');
                textMajor.Text = parts.Length > 0 ? parts[0] : String.Empty;
                textMinor.Text = parts.Length > 1 ? parts[1] : String.Empty;
                textDate.Text = parts.Length > 2 ? parts[2] : String.Empty;
                textRevision.Text = parts.Length > 3 ? parts[3] : String.Empty;
            }
        }
    }
}

As you can see, there’s not much to this editor. But the point of this post is to teach you how to connect this simple editor to the process parameter.

The next step is to create a class derived from UITypeEditor that will open our dialog. Here’s the code:

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

namespace CustomWorkflowTypes
{
    class SimpleVersionEditor : UITypeEditor
    {
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            if (provider != null)
            {
                IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));

                if (editorService == null)
                {
                    return value;
                }

                using (SimpleVersionDialog dialog = new SimpleVersionDialog())
                {
                    dialog.Version = value as String;

                    if (editorService.ShowDialog(dialog) == DialogResult.OK)
                    {
                        value = dialog.Version;
                    }
                }
            }

            return value;
        }

        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }
    }
}

Note that the EditValue method opens the dialog using the value provided and then resets the value if the user hit OK. You may also notice that we use the IWindowsFormsEditorService to show the dialog. This assures that the window is parented correctly. (You could also open a WPF window here, but it requires more work to parent correctly.)

The GetEditStyle method tells the property grid what type of button to show. The other choice is a drop down button. More on that in a later post.

So, we have a dialog and a UITypeEditor implementation. But what do we do now?

The last step is to change the metadata so that our editor is used instead of the default String editor. I have also changed the name of the process parameter to VersionNumber. Here’s the new XAML:

 <Activity x:Class="TfsBuild.Process" 
  xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities" 
  xmlns:mtbc="clr-namespace:Microsoft.TeamFoundation.Build.Client;assembly=Microsoft.TeamFoundation.Build.Client" 
  xmlns:mtbw="clr-namespace:Microsoft.TeamFoundation.Build.Workflow;assembly=Microsoft.TeamFoundation.Build.Workflow" 
  xmlns:mtbwa="clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow" 
  xmlns:s="clr-namespace:System;assembly=mscorlib" 
  xmlns:this="clr-namespace:TfsBuild;" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
  <x:Members>
    <x:Property Name="VersionNumber" Type="InArgument(x:String)" />
    <x:Property Name="Verbosity" Type="InArgument(mtbw:BuildVerbosity)" />
    <x:Property Name="Metadata" Type="mtbw:ProcessParameterMetadataCollection" />
    <x:Property Name="SupportedReasons" Type="mtbc:BuildReason" />
  </x:Members>
  <this:Process.Verbosity>[Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal]</this:Process.Verbosity>
  <this:Process.Metadata>
    <mtbw:ProcessParameterMetadataCollection>
      <mtbw:ProcessParameterMetadata 
            Category="Basic" 
            Description="The Version number of the build." 
            DisplayName="Version Number"
            Editor="CustomWorkflowTypes.SimpleVersionEditor,CustomWorkflowTypes"
            ParameterName="VersionNumber" />
    </mtbw:ProcessParameterMetadataCollection>
  </this:Process.Metadata>
  <this:Process.SupportedReasons>All</this:Process.SupportedReasons>
  <Sequence>
    <mtbwa:WriteBuildMessage DisplayName="Write message" Message="[VersionNumber]" Importance="High" />
  </Sequence>
</Activity>

Notice the new Editor attribute on the ProcessParameterMetadata. The format is simply the full type name, a comma, and the assembly name without the .dll extension. If you try this out now, you will find that it doesn’t work. There is no custom editor associated with the parameter. This is because the assembly “CustomWorkflowTypes” cannot be found by Visual Studio.

There are a couple of ways to fix the problem so that Visual Studio can find our assembly:

1) We could manually place the assembly on all clients. Putting it in the Comon7\IDE\PrivateAssemblies folder under where Visual Studio is installed. This would work as is and would not require any changes to our assembly.

2) Or, we could add a custom activity to our assembly and check it into the custom assembly path for our controller. This would automatically put the assembly on all clients and build machines when and if it is needed.

For more information on how to set up a custom assembly path and add a custom assembly, see Jim’s blog post.

I hope this explains how to use the Editor property of the Process Parameter Metadata. In the next post, we will discuss how to add a custom type and use that instead of just having a String.