(Many thanks to Hayder Casey, who provided this great write-up on work item tracking custom controls. He’s a developer who works on the TFS Work Item Tracking team with me. Thanks Hayder!)

Custom Controls provide a mechanism to extend the work item form to include user developed controls. It enables various scenarios, that are difficult or not possible using the built in controls such as: extend event-based rules, custom user interface, workflow capabilities. More details about custom control capabilities and restrictions are found in the WIT Custom Controls msdn article.

We have a codeplex project: Custom Control for TFS Work Item Tracking, you can find more details, and source code for useful custom controls.

Implementation

Custom control should derive from System.Windows.Forms.Control and implement IWorkItemControl interface and optionally implement IWorkItemToolTip, IWorkItemUserAction, and IWorkItemClipboard.  These interfaces are in Microsoft.TeamFoundation.WorkItemTracking.Controls.dll

IWorkItemControl

This is the interface that all custom control (actually even built in controls) implements. It provides basic functionality to interact with the workitem object and the form. Let’s take a look at its definition:

 

    public interface IWorkItemControl

    {

        object WorkItemDatasource { get; set; }

        string WorkItemFieldName { get; set; }

        StringDictionary Properties { get; set; }

        bool ReadOnly { get; set; }

 

        event EventHandler AfterUpdateDatasource;

        event EventHandler BeforeUpdateDatasource;

 

        void Clear();

        void FlushToDatasource();

        void InvalidateDatasource();

        void SetSite(IServiceProvider serviceProvider);

    }

 

WorkItemDatasource
Passes a reference to the WorkItem object (cast it to Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem). During initialization this get set to null, so make you take that into account when using the property.

 

WorkItemFieldName

The name of the field the control is associated with. However it’s possible for control to not be associated with a specific field. For example the LinksControl always work with the work item links and not associated with a field while the DateTimeControl is always associated with a field.

 

Properties

A property bag of all attributes specified in the work item form xml for this control. There are a set of standard attributes (Padding, Margin, Dock, …etc ) see The Control element article on msdn for a complete list of attributes. These standard attributes are automatically parsed and applied to your control, so you don’t need to do anything. Besides those you can supply custom attributes, there are no restriction to the attribute name. For example you can specify a color attributes in the xml, then in your implementation you can read this attributes and set your control color accordingly. In 2010 we also allow you to specify an xml element “CustomControlOptionsType”.

 

Here is the control type schema used for the wit definition, highlighted is new for 2010:

    <xs:complexType name="ControlType">

       

 <xs:element name="CustomControlOptions" type="CustomControlOptionsType" minOccurs="0" maxOccurs="1" />

       

<xs:attribute name="FieldName" type="typelib:ReferenceFieldName" use="optional" />

        <xs:attribute name="Type" type="ValidControlsType" use="required" />

        <xs:attribute name="Label"     type="LabelType" use="optional" />

        <xs:attribute name="LabelPosition" type="LabelPositionType" />

        <xs:attribute name="Dock"     type="DockType" use="optional" />

        <xs:attribute name="Padding" type="PaddingType" use="optional" />

        <xs:attribute name="Margin" type="PaddingType" use="optional" />

        <xs:attribute name="ReadOnly" type="ReadOnlyType" use="optional" />

        <xs:attribute name="MinimumSize" type="SizeType" use="optional" />

        <xs:attribute name="Name" type="NonBlankString" use="optional" />

        <xs:anyAttribute processContents="skip"/>

    </xs:complexType>

 

    <xs:complexType name="CustomControlOptionsType">

      <xs:sequence>

        <xs:any processContents="skip" minOccurs="0" maxOccurs="unbounded"/>

      </xs:sequence>

    </xs:complexType>

 

 

The CustomControlOptions is accessible to you control using IWorkItemControl.Properties[“innerxml”].

 

ReadOnly

Tells the control to display in read only mode.

 

BeforeUpdateDatasource/AfterUpdateDatasource

Implement and raise those events when before and after you flush user input to the datasource.

 

Clear

Clear the control content.

 

FlushToDatasource

Control is requested to flush any data to workitem object. This usually happens during save operation. In most cases data should be written to workitem immediately on user change.

 

InvalidateDatasource

Asks control to invalidate the contents and redraw. At this point, control can read from work item object and display/refresh data.

 

SetSite

Gives pointer to IServiceProvider if you intended to access DocumentService or other VS Services. If services are not needed, do nothing in this method.

 

We have added a new service in 2010 IWorkItemControlHost. Simply call serviceProvider.GetService(typeof(IWorkItemControlHost))). Here is the interface definition, might be useful to you if you want to interact with Microsoft Office:

    [Guid("c823b363-9cb6-4be4-baa3-bc4154409114")]

    public interface IWorkItemControlHost

    {

        void ShowWorkItem(WorkItem workItem);

        void ShowQueryResults(TfsTeamProjectCollection teamProjectCollection, string teamProjectName, string queryText);

        void SendToExcel(TfsTeamProjectCollection teamProjectCollection, string teamProjectName, List<string> fieldNames, List<int> workItemIds);

        void SendToProject(TfsTeamProjectCollection teamProjectCollection, string teamProjectName, List<string> fieldNames, List<int> workItemIds);

        void SendToMail(TfsTeamProjectCollection teamProjectCollection, string teamProjectName, List<string> fieldNames, List<int> workItemIds);

        bool SupportsSendToExcel { get; }

        bool SupportsSendToProject { get; }

        bool SupportsSendToMail { get; }

    }

 

IWorkItemToolTip

Implement this interface if you want the custom control’s Label to have a tooltip.

 

    public interface IWorkItemToolTip

    {

        ToolTip ToolTip { get; set; }

        Label Label { get; set; }

    }

 

When both the ToolTip and the Label are set, just call ToolTip.SetToolTip(Label, "This is a custom tool tip”). Well you probably want to make it useful, the standard control put the field reference name in the tooltip. You could also do something complicated such as showing the field history in the tooltip.

 

IWorkItemUserAction

Implement this if your control might require user action. user action required means the item is in a bad state and cannot be saved.

 

    public interface IWorkItemUserAction

    {

        string RequiredText { set; get; }

        Color HighlightBackColor { set; get; }

        Color HighlightForeColor { set; get; }

        bool UserActionRequired { get; }

        event EventHandler UserActionRequiredChanged;

    }

 

RequiredText: Friendly error message, will be displayed in the infobar in VS.

HighlightBackColor/ForeColor: use these colors in your control if a user action is required so its consistent with the rest of controls on the form.

UserActionRequired: tell them form if the control need input from the user

UserActionRequiredChanged: Raise this event if the UserActionRequired Property changes

 

IWorkItemClipboard

Implement this if you want your control to work with VisualStudio edit menu (Cut/Copy/Paste). Basically fire ClipboardStatusChanged if any of the Can* method might change.

    public interface IWorkItemClipboard

    {

        bool CanCut { get; }

        bool CanCopy { get; }

        bool CanPaste { get; }

        void Cut();

        void Copy();

        void Paste();

        event EventHandler ClipboardStatusChanged;

    }

 

Deployment

 

Adding the custom control to the form definition

To use the control in a form you will need to modify the form definition on the server. the easiest way to download a form you want to modify using the exportwitd command, modify it to use the custom control then upload the modified xml using importwitd. The tool to import/export work item definition is witadmin.exe (Run  “witadmin help importwitd “and “witadmin help exportwitd” to get help on using those commands). To specify custom control in the form simply locate the Form section in the type xml and modify or add a Control element. Here is an example of using a MultiValueControl from the codeplex project:


<Control Type="MultiValueControl" FieldName="Microsoft.VSTS.Common.Triage" Label="Triag&amp;e:" LabelPosition="Left" />

This is just like any other control definition, the syntax is the same, in fact, the import tool (for the most part) doesn’t distinguish between built-in control and a custom controls, it’s just a name for a control.

 

Deployment Folders

After you created a custom control (as part of a dll or an exe) you will need to drop both the dll and wicc file in one of the custom control folder both should be In the same folder unless the wicc file specifies a full path to the assembly.

 

Here are the locations that we look for custom controls in Visual Studio 2010. We search in this order. If one is found in one of these folders we will use it and not look further:

1.       All ValueNames under " HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\WorkItemTracking\WorkItemTracking\CustomControls\LookInFolders\". So if you want to store the .wicc and dll in some custom folder (eg c:\program files\MyTool\) you can, by adding this path as a value name under the above reg key.

2.       Environment.SpecialFolder.CommonApplicationData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0\

3.       Environment.SpecialFolder.LocalApplicationData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0\

4.       PrivateAssemblies Folder (eg C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies)

5.       Environment.SpecialFolder.CommonApplicationData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\

6.       Environment.SpecialFolder.LocalApplicationData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\

Location 5, and 6 is a fall back that VS2010, 2008 and 2005 look under for custom control so we recommend not using those folders to keep the 2008 or 2010 version of the control. Ideally if you have VS2008 and 2010 side by side, you should drop the custom controls for 2008 in one of these folders:

1.       Environment.SpecialFolder.CommonApplicationData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\9.0\

2.       Environment.SpecialFolder.LocalApplicationData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\9.0\

 

Wicc File

This file tell us where the custom control is. The filename is the name of the control. For example if in the XML the contorlType is specific as MultiValueControl, we will look for MultiValueControl.wicc file in the custom controls folders.

 

Here is a typical content of a .wicc file:

 

 

<?xml version="1.0"?>

<CustomControl xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <Assembly>WitCustomControlLibrary.dll</Assembly>

  <FullClassName>WitCustomControl.MultiValueControl</FullClassName>

</CustomControl>

 

 

The xml file basically says which assembly (can be full path path) and the FullClassName of the custom control.

 

Making it easier

We made some changes in 2010 that makes working with custom controls easier. Keep in mind these changes only applies to VS 2010, we may remove support for these in the next version in favor for a more sophisticated mechanism.

In the control definition, you can specify a PreferredType that will be used instead of the standard type if was installed on the client. Say you have an IdentityControl that you like to use it for the AssignedTo field. Here is how you define specify that in the form xml:


<Control Type="FieldControl" PreferredType=”IdentityControl” FieldName="System.AssigedTo" Label="Assigned To” LabelPosition="Left" />


Again, if a client doesn’t have IdentityControl deployed it will fall back to using the standard FieldControl. VS2008 client will just ignore the PreferredType and use the Type instead.