A Re-Usable Button Control for TFS Work Item Definitions

There’s plenty of documentation available on MSDN and from other sources that discusses how to create custom controls to be used by TFS Work Item Type (WIT) definitions.  I was working with a customer recently and I needed to customize a work item to show a button that would launch a custom user interface.  The custom UI would create an associated Code Review work item and a shelveset from the user’s current pending changes.  I thought there might be an existing implementation of a custom WIT button control but alas, after scouring the web and other resources I couldn’t find any.  So I started down the path of writing my own.

As mentioned above there is already a wealth of documentation detailing how to create custom controls, so I won’t get into most of those details.  Instead I’ll focus on the code for the button control and how you can re-use the control to add similar functionality to your work item types.

The implementation turns out to be pretty straight forward.  I create a new control library and add a new control that derives from UserControl and implements the IWorkItemControl interface.  The IWorkItemControl interface implementation is shown below.

  1: protected IServiceProvider _serviceProvider;
  2: void IWorkItemControl.SetSite(IServiceProvider serviceProvider)
  3: {
  4:     _serviceProvider = serviceProvider;
  5: }
  6:  
  7: protected WorkItem _workItem;
  8: object IWorkItemControl.WorkItemDatasource
  9: {
  10:     get { return _workItem; }
  11:     set { _workItem = (WorkItem)value; }
  12: }
  13:  
  14: public event EventHandler AfterUpdateDatasource;
  15:  
  16: public event EventHandler BeforeUpdateDatasource;
  17:  
  18: public void Clear()
  19: {
  20: }
  21:  
  22: public void FlushToDatasource()
  23: {
  24: }
  25:  
  26: public void InvalidateDatasource()
  27: {
  28: }
  29:  
  30: public System.Collections.Specialized.StringDictionary Properties
  31: {
  32:     get { return _properties; }
  33:     set { _properties = value; }
  34: }
  35:  
  36: public bool ReadOnly
  37: {
  38:     get { return _readonly; }
  39:     set { _readonly = value; }
  40: }
  41:  
  42: public string WorkItemFieldName
  43: {
  44:     get { return _fieldName; }
  45:     set { _fieldName = value; }
  46: }

With the interface implemented, I then simply add a new button control to the custom control with some default settings as shown.

  1: private Button _button;
  2:  
  3: public WitButton()
  4: {
  5:     InitializeComponent();
  6:     InitControl();
  7: }
  8:  
  9: private void InitControl()
  10: {
  11:     if (_button != null)
  12:         return;
  13:  
  14:     _button = new Button();
  15:     _button.Text = "Default Text";
  16:     _button.Width = 125;
  17:     _button.Top = 10;
  18:     _button.Click += new EventHandler(_button_Click);
  19:  
  20:     base.Controls.Clear();
  21:     base.Controls.Add(_button);
  22: }

Finally, I expose the Text and Width properties of the internal button control so that they can be set by any derived controls.  I also expose an event that is fired when the button is clicked.

  1: public string ButtonText
  2: {
  3:     get { return _button.Text; }
  4:     set { _button.Text = value; }
  5: }
  6:  
  7: public int ButtonWidth
  8: {
  9:     get { return _button.Width; }
  10:     set { _button.Width = value; }
  11: }
  12:  
  13: public delegate void ButtonClickHandler(object sender, EventArgs args);
  14: public event ButtonClickHandler ButtonClick;
  15:  
  16: void _button_Click(object sender, EventArgs e)
  17: {
  18:     if (ButtonClick != null)
  19:         ButtonClick(sender, e);
  20: }

That’s it for the base control, which by itself won’t do much, so there is no *.wicc file necessary at this point.

Now I need to create a derived control that will set the base control’s button text and width accordingly and handle the ButtonClick event to actually perform some work when the user clicks the button.  A simple example of this is shown in the next snippet.

  1: public partial class MyButton : WitButton
  2: {
  3:     public MyButton()
  4:     {
  5:         InitializeComponent();
  6:         this.ButtonText = "Do Something";
  7:         this.ButtonWidth = 150;
  8:         this.ButtonClick += new ButtonClickHandler(MyButton_ButtonClick);
  9:     }
  10:  
  11:     void MyButton_ButtonClick(object sender, EventArgs args)
  12:     {
  13:         MessageBox.Show("Button was clicked");
  14:     }
  15: }

With the code in place I create a *.wicc file named MyCustomWitButton and deploy it along with the binaries for my custom control and the base control to Environment.SpecialFolder.CommonApplicationData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0\ (there are multiple folders which are probed to locate custom WIT controls as explained here).  Now I can add the derived button to my Bug work item type definition (no field name or label is required for the button control).

image

After saving the work item type definition I can now create a new Bug within the team project and I should get the following result.

image

Finally I can get to the original problem I was trying to solve, which was using a button from the work item form to launch a custom UI to create an associated Code Review work item and shelveset.  I created a WPF application and launch it on the ButtonClick event.  I am also leveraging the protected _workitem and _serviceProvider members from the base control to access the associated Bug work item and the TFS work item DocumentService respectively.  The results are shown below.

image

I’ve created a project on CodePlex where you can download the source for the base control and a sample derived control.