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="http://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="http://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.

Comments (12)

  1. Vaccano says:

    This looked useful, but it did not work.

    I made a Winforms app like you did and copied it to:

    C:Program FilesMicrosoft Visual Studio 10.0Common7IDEPrivateAssemblies

    on my build machine but I did not get a editor option.

    You mentioned it looking for a dll so I changed the output of the project to be a dll and it still did not work.

    I used your xaml exactly except I added BrowsableWhen="Always" and changed the name of the app (and editor) to VersionNumberEditor.

  2. Jason Prickett says:

    HI Vaccano,

    Do you mind posting this in our forum (http://social.msdn.microsoft.com/Forums/en-US/tfsbuild/threads)? There you could attach your XAML and code, so that I could take a look and see if there is an obvious problem.

    Sorry you are having trouble.

    Thanks,

    Jason

  3. tim says:

    Thanks Jason, this is a very helpful post.  I hope you wouldn't mind answering a few questions:

    1. Does the Editor have to be of type UITypeEditor ?

    2. How can I create an editor using purely WPF ?

  4. Daniel says:

    Hi Jason,

    Thanks for the nice post.

    Somewhere in the middle you write that there will follow some information about a drop down editor in a later post. I would like to kindly ask if there is some news on that.

    Regards,

    Daniel

  5. Padda says:

    I'm finding that this doesn't work either. When I click upon the textbox in the Edit Build Definition screen, the editor does not appear.

    Regards,

    jP

  6. Padda says:

    Hi,

    Any help with above much appreciated.

    jP

  7. Brandon says:

    Any update on how to create a drop down button for a process parameter input?

    Thanks!

  8. Amy G. Grossman says:

    Hi Jason,

    I have a custom wizard that I created for building from multiple projects and definitions and automated to deploy to a specific environment.  It was loading the wizard before, but then I changed it so that it would pull existing process data and now it doesn't load the wizard at all.  Where should I look?  Thanks!

    Amy

  9. Jason Prickett says:

    Hi Amy,

    Without seeing the changes, I am not really sure where you should start. You may want to post your code on the TFS forums or contact our support team. If you post it on the forums, reply with the link here and I will try to take a look.

    Thanks,

    Jason

  10. sriprasanna says:

    Is it possible to show the sub elements of a custom datatype in the TFS build definition file? For example in the 'Agent setting' the menu expands to its sub elements in the same way I want to show the elements of my own datatype.

    <x:Members>

    <x:Property Name="BuildNumberFormat" Type="InArgument(x:String)" />

    <x:Property Name="SolutionSpecificBuildOutputs" Type="InArgument(x:Boolean)" />

    <x:Property Name="CleanWorkspace" Type="InArgument(mtbwa:CleanWorkspaceOption)" />

    <x:Property Name="SourceAndSymbolServerSettings" Type="InArgument(mtbwa:SourceAndSymbolServerSettings)" />

    **<x:Property Name="AgentSettings" Type="InArgument(mtbwa:AgentSettings)" />**

    <x:Property Name="AssociateChangesetsAndWorkItems" Type="InArgument(x:Boolean)" />

    <x:Property Name="CreateWorkItem" Type="InArgument(x:Boolean)" />

    <x:Property Name="CreateLabel" Type="InArgument(x:Boolean)" />

    <x:Property Name="GetVersion" Type="InArgument(x:String)" />

    <x:Property Name="PrivateDropLocation" 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:Property Name="BuildProcessVersion" Type="x:String" />

    **<x:Property Name="MapBuildConfig" Type="InArgument(ma:MyBuildConfig)" />**

    In above code "AgentSettings" expands to its elements: Name filter, Tags filter so on.. But my custom type "MyBuildConfig does not expand to its elements…

    Any help?

    Thanks!

  11. Jason Prickett says:

    To have your properties be expandable in the WinForms property grid, you have to put the ExpandableObjectConverter on them as a typeconverter. Look for examples of this converter and you should find plenty of examples.

    Thanks,

    Jason

  12. Albert Shamsiyan says:

    Hi Jason

    I'm failing to cause this code to "save" the inserted information.

    Any idea?

    Thx