Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Expression Control for TFS Work Items

0.00/5 (No votes)
5 Oct 2010 1  
Custom control for TFS Work Items that shows the result of calculating an expression based on the work item fields contents

Introduction

This article presents a custom control for Team Foundation Server's Work Item forms. The control is a textbox that displays the result of a mathematical expression in which other numeric fields of the work item can participate.

Background

NOTE: This article is not intended to be a comprehensive guide on how to create/modify TFS work items or custom controls. For detailed information, please visit the links on the More Information section.

Team Foundation Server is a great platform for Application Lifetime Management. One of the features it provides is the ability to create and manage Work Items, which encapsulate single units of work.

TFS comes out of the box with standard work items defined for managing requirements, bugs, tasks, risks, and other artifacts commonly created when managing a software development project (these items are defined within two Project Templates, MSF for Agile Software Development and MSF for CMMI Process Improvement). However it is possible to define new work item types and to modify the existing work item types (and the same for the project templates).

Work items consist of Fields that store various kind of information; typical fields are the title, the name of the work item creator, the assigned priority, or the estimated implementation effort. In order to edit the information of these fields, each work item also defines the layout of a Form with various editable Controls (there are also non-editable controls, such as the work item change history). These forms are displayed by Team Explorer (a component of Visual Studio) when you create a new work item or when you edit and existing work item.

Team Explorer provides several ready made controls such as a simple one-line textbox, an HTML rich control, a date selector, a dropdown list, or a web browser. It is possible to create new types of controls and to use them in the TFS work item forms, and that's what you will see in this article.

So, What Is This?

In this article, you will meet ExpressionControl, a TFS custom control that shows the result of calculating a mathematical expression. The nice part is that you can reference other fields of the work item in the expression, and if you change the value of one of these fields, the expression is re-evaluated and the value on the control is automatically updated. If you associate a work item field to the control, the calculated value is stored within the work item itself, therefore being available in work item queries and reports.

As a proof of concept, I provide a modified version of the Risk work item which comes with the MSF for CMMI Process Improvement project template. In this version, I have replaced the Severity field with Impact, which is a numeric field that can take any value. I have also added a Exposure field, which is automatically calculated by ExpressionControl as probability multiplied by impact.

The control source code is provided as a Visual Studio 2010 solution which consists of one C# class library and one installer project. You can use the solution as a skeleton for creating your own TFS custom controls.

Installing and Using the Control

If you want to install and use ExpressionControl right now, follow these steps:

  1. Pretty obvious, but ensure that you have Team Explorer 2010 installed.
  2. Download the installer from the downloads section.
  3. Run the installer. This will just copy two .dll files and one .wicc file (which is just a XML file) to one special subfolder inside the Visual Studio installation folder.

Now you can upload the modified Risk work item definition to the server. You can do that by using the witadmin tool that comes with Team Explorer. Open a Visual Studio command prompt and execute:

witadmin importwitd /collection:(URL_of_your_TFS_server_and_collection) 
/p:(name_of_the_team_project) /f:Risk.wit

Or, if you have TFS Power Tools installed on Visual Studio, select Tools - Process Editor - Work Item Types - Import WIT on the Visual Studio menu.

Now try to create a new work item of type Risk. You will see that when you modify either the Probability or the Impact fields, the Exposure field is updated automatically:

Animation on how modifying Probability or Impact cause the Exposure to be recalculated

More generally, the way of using the control in a work item is by defining a new control of type ExpressionControl, and adding an attribute named Expression whose value is the expression to be calculated. The expression can contain basic operations (+ - * /) and the operands can be either numbers, or field names enclosed in brackets, as in [Priority]. For example, in the modified Risk definition we can find the following line:

<Control
   FieldName = "Konamiman.TFS.Exposure"
   Type = "ExpressionControl"
   Expression = "[Probability]*[Impact]"
   Label = "Exposure:"
   LabelPosition = "Left"
   ReadOnly = "True">

Associating a field to the control is not mandatory, but it is necessary if you want the calculated value to be persisted to the work item in the TFS database. For example, we can create a work item query that shows risks with priority, impact and exposure:

Sample of risk list query

For the modified Risk work item, we have a backing field defined as follows:

<FIELD name="Exposure" refname="Konamiman.TFS.Exposure" type="Integer">
    <HELPTEXT>Exposure of the risk, calculated as probability by impact.</HELPTEXT>
</FIELD>

You may want to define the field as double instead of integer, if you know that your calculation will give non-integer numbers. For example, you could have defined your expression as ([Probability]*[Impact])/100.

In order to insert a new field and control on an existing work item, you need to download the work item definition file from the server. With the witadmin tool, you would do:

witadmin exportwitd /collection:(URL_of_your_TFS_server_and_collection) 
/p:(name_of_the_team_project) /n:(work_item_type_name) /f:(destination_file_name>

Or with TFS Power Tools, select Tools - Process Editor - Work Item Types - Export WIT on the Visual Studio menu.

The Project

A TFS custom control is just a Windows Forms user control that implements the IWorkItemControl interface, accompanied by a descriptor file (.wicc) in XML format (see the links on the More Information section for details). Therefore the steps needed to create a custom control are the following:

  1. Create a new Visual Studio solution, of type Class Library.
  2. Add references to the System.Windows.Forms and System.Drawing system assemblies.
  3. Add references to the following Team Explorer assemblies:
    %ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\
    Microsoft.TeamFoundation.WorkItemTracking.Controls.dll
    %ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\
    v2.0\Microsoft.TeamFoundation.WorkItemTracking.Client.dll

    (Note: The root may be instead %ProgramFiles(x86)% on your system.)

  4. Add a new item of type User Control, and make it implement IWorkItemControl.
  5. Add the appropriate controls on the User Control design surface.
  6. Add the code for the IWorkItemControl (a few more details on this later).
  7. Add a new XML file, name it YourControlName.wicc (the name of the file will be the name that TFS will use to refer to your control) and set its compilation action to Content. The contents of the file must be as follows:

    <?xml version="1.0"?>
    <CustomControl xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance 
    	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Assembly>YourClassLibraryFileName.dll</Assembly>
      <FullClassName>YourControlFullClassName</FullClassName>
    </CustomControl>

Once you have compiled your code, you need to copy the resulting .dll file, together with the .wicc file, to the following location:

%ProgramData%\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0

And that's it, Team Explorer will recognize your control and display it, as long as you have declared it in a work item.

The Installer

Although deploying your control is as easy as copying two files to the TFS custom control folder as shown above, it is not a bad idea to have an installer project to do the job. Nick Hoggart explains how to create such project, however since I have done a couple more things than him, I will describe here the full process I have followed for creating the installer:

  1. Create a new project in the solution, of type Setup project.
  2. Open the File System Editor tab, and add a new folder of type Custom Folder, named CommonAppDataFolder. Set its DefaultLocation property to [CommonAppDataFolder].
  3. Under the CommonAppData folder, create the hierarchy for the folder Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0, one folder at a time. See the following screenshot, borrowed from Hoggart's page itself:

    Folder hierarchy created in the setup project

  4. Right click on the 10.0 folder, and select Add - Project Output. Select Primary Output and Content Files from your class library project (optionally you can select Debug Symbols as well, this will be useful if you want to debug your code).
  5. On the Detected Dependencies folder of the setup project in Solution Explorer, select all the Microsoft.TeamFoundation.* dependencies that have been detected, right click and select Exclude, to avoid the installer copying them together with your library.
  6. On the User Interface Editor pane, remove the second and the third screen. These are the install location selection screen (not necessary, since files are always installed at a fixed location) and the install confirmation screen (useless if the install location selection screen has been removed). Unless of course you want to let the user choose if the install will be done for all users or only for the current user (I always set the InstallAllUser project property to True).

    Now we will add a launch condition to avoid the installer to run if Team Explorer is not installed. This is strictly not necessary but will make our installer a bit neater.

  7. Open the Launch Conditions Editor tab.
  8. Right click on Search Target Machine, then select Add Registry Search.
  9. Select the registry search just added, open the propertieswindow, and set the properties as follows: Name = Team Explorer 2010, Property = TEAMEXPLORERINSTALLED, Root = vsdrrHKCU, RegKey = Software\Microsoft\VisualStudio\10.0_Config\InstalledProducts\Team Explorer. See the following screenshot:

    How the registry search condition is to be configured

  10. Right click on Launch Conditions, then select Add Launch Condition.
  11. Select the launch condition just added, open the properties window, and set the properties as follows: Name = Team Explorer 2010, Condition = TEAMEXPLORERINSTALLED, Message = Error: Team Explorer 2010 is not installed. See the following screenshot:

    How the launch condition is to be configured

The last step would be to appropriately configure the setup project properties (Author, Description, URLs, and the like). You can pretty much adapt the provided setup project (and the class library project, for that matter) for your own use by changing "Konamiman" into your own name or company name.

The Code

Now I'll explain the key points of the control code itself. You can download the Visual Studio solution if you want to take a closer look.

The Expression Evaluator

First of all, I must say that I have not crafted my own expression evaluator. Instead, I have used the excellent Sebastien Ros' State of the Art Expression Evaluation, which provides all the functionality I needed and even more. I recommend you to take a look at Sebastien's article, but here are the basic points you need to know in order to understand how the evaluator is used in ExpressionControl:

  • The core of the evaluator is the Expression class. You pass a string with the expression to be evaluated when you instantiate the class, for example:
    var expression = new Expression("([Priority] * [Impact]) / 100");
  • You use the Evaluate method of the Expression object in order to trigger the expression evaluation. The method returns an object with the result of the evaluation (ExpressionControl handles numbers only, but the Expression class can handle strings, booleans and DateTimes as well).
  • The Expression class can handle parameters, that is, alphanumeric identifiers (enclosed in brackets) that are resolved dynamically when the expression is evaluated. When a parameter is found during the evaluation, an event named EvaluateParameter is fired. This event gives the parameter name, and expects the parameter value to be set on one of the properties of the associated EventArgs. For example:
    Expression e = new Expression("2 * [Pi]");
    
    e.EvaluateParameter += delegate(string name, ParameterArgs args)
    {
        if (name == "Pi") {
            args.Result = 3.14;
        }
    };
    
    var result = e.Evaluate();

The source code of the expression evaluator is embedded in the ExpressionControl project itself, under the folder ExpressionEvaluator. The evaluator code depends on a library, Evaluant.Antlr.dll, which is referenced by the project (and of course must be copied together with the control library at install time).

The Control Code

The ExpressionControl visual surface is just a single-line TextBox used to display the results of the expression evaluation. It will be read-only or not, depending on how you configure the ReadOnly property of control in the containing Work Item form (of course, it is recommended to configure it as read-only). Team Explorer creates an instance of the control when you create a new work item or when you select an existing work item for edition, provided that the control is declared in the work item form definition.

I will now enumerate the main members of the ExpressionControl class, explaining the key points of each one so that you can get a general idea of how the control works.

  • IWorkItemControl.Properties: This is a StringDictionary which contains all the attributes of the control, as declared in the XML element Control on the work item form definition (all the XML attributes of the Control element are included here). When this property is set, we retrieve the Expression attribute and use it to instantiate an Expression object, whose EvaluateParameter event is assigned to a handling method (expression_EvaluateParameter).
  • IWorkItemControl.WorkItemDatasource: This is the work item which is being edited by the form that contains the control (although the property is declared as Object, the value is always of type WorkItem). When this property is set, we assign a handler to the work item's FieldChanged event (method OnFieldChanged) so that we can appropriately re-evaluate the expression and update the control value if necessary.
  • IWorkItemControl.FlushToDatasource: This method is invoked when the control value has to be backed up to the associated field on the work item (so the value is persisted together with the rest of the work item data in the TFS database). We simply check that the control is actually associated to a work item field (if not, we haven't where to persist) and that the field is of numeric type (string fields are accepted too, in this case the value is simply ToString-ized before it is set), then we set the new value for the field.
  • GenerateNewTextboxValue: This method is invoked when the work item form is loaded (via the IWorkItemControl.InvalidateDatasource method) and when one of the fields referenced in the expression changes (via the OnFieldChanged method). It simply invokes the Evaluate method of the Expression object and returns the result as a string to be set on the TextBox (the result is also cached on the calculationValue class field, so that the FlushToDatasource method can use it). The method takes care of all possible error conditions, returning an appropriate error message instead of a calculation result if necessary.
  • expression_EvaluateParameter: This is invoked by the expression evaluator whenever it encounters a parameter in the expression. We assume that the parameter is a field name and query the work item for the field value. If the work item does not declare a field with such name, or if the field is not numeric, we throw an exception, which will be captured by the GenerateNewTextboxValue method and converted into an error message.

And that's pretty much it. There are a few more auxiliary members, but the core of the control is in the ones listed above.

Debugging

When I had the control finished, I found that it worked pretty well, except for one detail: when saving a work item that contained the control, Visual Studio would show me a "Object reference not set to an instance of an object" message box, otherwise saving the work item correctly. It turned out that the FieldChanged event is fired when the work item is about to be saved, but with the Field property of the WorkItemEventArgs argument set to null. I don't know if this is "bug or feature", but for what I see it seems that it is safe to just ignore the event when the Field property is null.

I would never have found the bug if I had not debugged the code. However you can't debug a TFS custom control directly as if it were an ordinary Visual Studio project (you can't "run" it). So, what can we do? MSDN has the answer (scroll down until you see the accepted answer). Just a note: you have to skip steps 10 and 11 (you can't "start" a class library project). For completeness, I paste here the procedure (the original refers to VS 2008, I have changed the references to VS 2010):

  1. Open up VS 2010 and load your project.
  2. Delete all breakpoints in your project.
  3. Right click on the project and go to the project properties.
  4. For the configuration, select Active (Debug).
  5. Under the enable debuggers section, check Enable unmanaged code debugging and enable the Visual Studio hosting process.
  6. Save.
  7. Right click on your project and click Clean.
  8. Right click on your project again and click Rebuild.
  9. In Windows Explorer, go to debug folder and copy the output (.dll, .exe., etc.) to the C:\Documents and Settings\All Users\Application Data\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0 (this is where TF 2010 WI looks for the .dll and .wicc files).
  10. (Skip this!) On your toolbar with the drop down set to debug, click the arrow.
  11. (Skip this!) You will get a mini screen with your control on it. There should be a load button. Click on it and make sure you load the .dll/.exe from the path specified in step 9.
  12. Leave it open and don't close anything. Open up another instance of VS 2010.
  13. In Team Explorer, open the Work item type that contains your custom control (Add Work Item-> . . . . . ).
  14. Go back to the instance of VS 2010 in step 1 and go to Debug->Attach to Process.
  15. In the attach to Process Window, leave the Transport as Default and the Attach To field to have checked managed code and native code.
  16. In the Available processes window, you will see the process devenv.exe with an ID and the title of the Work Item you just created. Select that row and click on Attach.
  17. You will see some processing in the background as it is loading symbols. Once it is done, set your breakpoints accordingly.
  18. Go back to your 2nd instance of VS2010 (step 12) and perform what you need to perform on the Work item you created in step 13 to make sure the break point gets hit.
  19. Once your feel the breakpoint is hit, go back to the 1st instance of VS2010 (step 1 and step 17) and you will see the breakpoints you set hit. Now all you have to do is debug (sigh!).

More Information

If you want to learn more about TFS work item customization and about TFS custom control, take a look at these resources:

Final Note

This is my first TFS custom control, therefore the code may contain mistakes. If that's the case, or if you have improvement suggestions, please drop me a comment.

History

  • 1 October, 2010: First version
  • 6 October, 2010: Screenshots updated, now all the Visual Studio labels are displayed in English

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here