tech·nic·al·ly agile

Creating a WPF Work Item Control

Learn to create a custom WPF Work Item Control for Visual Studio Team System, enhancing user interactions with requirements and change requests effectively.

Published on
9 minute read
Image
https://nkdagility.com/resources/4vBEBY-yvDB

I have a little custom control I need added to my Visual Studio Team System  projects. This control will allow specific groups of users as representatives of Advocacy groups with in the life cycle model to sign off a Requirement or Change Request from within Visual Studio.

But, just to make things a little more fun, I wanted to create the custom work item tracking control using WPF.

In order to do this you need a wrapper control that allows you to load any WPF control that inherits from IWorkItemControl.

Here is an example WPF Work Item Control:

[ Get SignOffControl.xaml.vb source  ] [ Get full source  ] [ Get latest full source  ]

1   1: Imports Microsoft.TeamFoundation.WorkItemTracking.Client
1   2: Imports Microsoft.TeamFoundation.WorkItemTracking.Controls
1   3: 'Imports Microsoft.VisualStudio.TeamFoundation.WorkItemTracking
1   4: 
1   5: Namespace SignOff
1   6: 
1   7:     Partial Public Class SignOffControl
1   8:         Implements IWorkItemControl
1   9: 
1  10: #Region " IWorkItemControl "
1  11: 
1  12:         Public Event AfterUpdateDatasource(ByVal sender As Object, ByVal e As System.EventArgs) Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.AfterUpdateDatasource
1  13:         Public Event BeforeUpdateDatasource(ByVal sender As Object, ByVal e As System.EventArgs) Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.BeforeUpdateDatasource
1  14: 
1  15:         Protected m_serviceProvider As IServiceProvider = Nothing
1  16:         Protected m_workItem As WorkItem = Nothing
1  17:         Protected m_workItemFieldName As String = Nothing
1  18:         Protected m_properties As System.Collections.Specialized.StringDictionary
1  19: 
1  20: 
1  21:         Public Property [ReadOnly]() As Boolean Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.ReadOnly
1  22:             Get
1  23:                 Return Me.IsEnabled
1  24:             End Get
1  25:             Set(ByVal value As Boolean)
1  26:                 Me.IsEnabled = value
1  27:             End Set
1  28:         End Property
1  29: 
1  30:         Public Sub SetSite(ByVal serviceProvider As System.IServiceProvider) Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.SetSite
1  31:             m_serviceProvider = serviceProvider
1  32:         End Sub
1  33: 
1  34:         Public Property WorkItemDatasource() As Object Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.WorkItemDatasource
1  35:             Get
1  36:                 Return m_workItem
1  37:             End Get
1  38:             Set(ByVal value As Object)
1  39:                 If value Is Nothing And Not m_workItem Is Nothing Then
1  40:                     RemoveHandler m_workItem.FieldChanged, AddressOf OnFieldChanged
1  41:                 End If
1  42:                 m_workItem = value
1  43:                 If Not m_workItem Is Nothing Then
1  44:                     AddHandler m_workItem.FieldChanged, New WorkItemFieldChangeEventHandler(AddressOf OnFieldChanged)
1  45:                 End If
1  46:             End Set
1  47:         End Property
1  48: 
1  49:         Public Property WorkItemFieldName() As String Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.WorkItemFieldName
1  50:             Get
1  51:                 Return m_workItemFieldName
1  52:             End Get
1  53:             Set(ByVal value As String)
1  54:                 m_workItemFieldName = value
1  55:             End Set
1  56:         End Property
1  57: 
1  58:         Public Property Properties() As System.Collections.Specialized.StringDictionary Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.Properties
1  59:             Get
1  60:                 Return m_properties
1  61:             End Get
1  62:             Set(ByVal value As System.Collections.Specialized.StringDictionary)
1  63:                 m_properties = value
1  64:             End Set
1  65:         End Property
1  66: 
1  67:         Public Sub Clear() Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.Clear
1  68: 
1  69:         End Sub
1  70: 
1  71:         Public Sub FlushToDatasource() Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.FlushToDatasource
1  72: 
1  73:         End Sub
1  74: 
1  75:         Public Sub InvalidateDatasource() Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.InvalidateDatasource
1  76: 
1  77:         End Sub
1  78: 
1  79: #End Region
1  80: 
1  81: 
1  82:         Protected Sub OnFieldChanged(ByVal sender As Object, ByVal e As WorkItemEventArgs)
1  83: 
1  84:         End Sub
1  85: 
1  86:     End Class
1  87: 
1  88: End Namespace

It does not yet do anything, but the base wrapper is there. The visual elements only contain a couple of button to get me started testing the wapper and to make sure that the control is displayed:

[ Get SignOffControl.xaml source  ] [ Get full source  ] [ Get latest full source  ]

1   1: <UserControl x:Class="SignOff.SignOffControl"
1   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
1   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="317" Height="46">
1   4:     <StackPanel>
1   5:         <Button>SignOff</Button>
1   6:         <Button>Clear</Button>
1   7:     </StackPanel>
1   8: </UserControl>

This will display 2 buttons, but does nothing.

Creating a WPF Work Item Control  

You then need to create your wrapper control. This is a forms control that has the Element Host forms control to host the button. I have gone down the generic rout to minimise the amount of code I would use to create an individual “stub” when creating multiple controls.

here is an example “stub” which is created as a simple class:

[ Get SignOffHostControl.vb source  ] [ Get full source  ] [ Get latest full source  ]

1   1: Namespace SignOff
1   2: 
1   3:     Public Class SignOffHostControl
1   4:         Inherits WitCustomControlBase(Of SignOffControl)
1   5: 
1   6:     End Class
1   7: 
1   8: End Namespace

note: although this inherits from user control you will not be able to view it in the designer because of the generic nature of its inheritance. This is OK and does not hamper development.

  Creating a WPF Work Item Control  

All the heavy lifting for this control is done in the WitCustomControlBase and the generic type passed needs to meet the requirements of New, UIElement and IWorkItemControl. This ensures that it is a WPF control that inherits from IWorkItemControl.

To create this control you need to create a new Windows Forms control and call it WitCustomControlBase. Add a WPF “Element Host” to it and make that host dock to the total area.

Then we need to make the designer generic.

[ Get WitCustomControlBase.Designer.vb source  ] [ Get full source  ] [ Get latest full source  ]

1   1: Imports Microsoft.TeamFoundation.WorkItemTracking.Client
1   2: Imports Microsoft.TeamFoundation.WorkItemTracking.Controls
1   3: 
1   4: <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
1   5: Partial Class WitCustomControlBase(Of TWitWpfCustomControl As {New, UIElement, IWorkItemControl})
1   6:     Inherits System.Windows.Forms.UserControl
1   7: 
1   8:     'UserControl overrides dispose to clean up the component list.
1   9:     <System.Diagnostics.DebuggerNonUserCode()> _
1  10:     Protected Overrides Sub Dispose(ByVal disposing As Boolean)
1  11:         Try
1  12:             If disposing AndAlso components IsNot Nothing Then
1  13:                 components.Dispose()
1  14:             End If
1  15:         Finally
1  16:             MyBase.Dispose(disposing)
1  17:         End Try
1  18:     End Sub
1  19: 
1  20:     'Required by the Windows Form Designer
1  21:     Private components As System.ComponentModel.IContainer
1  22: 
1  23:     'NOTE: The following procedure is required by the Windows Form Designer
1  24:     'It can be modified using the Windows Form Designer.
1  25:     'Do not modify it using the code editor.
1  26:     <System.Diagnostics.DebuggerStepThrough()> _
1  27:     Private Sub InitializeComponent()
1  28:         Me.uxElementHost = New System.Windows.Forms.Integration.ElementHost
1  29:         Me.uxWitWpfCustomControl = New TWitWpfCustomControl
1  30:         Me.SuspendLayout()
1  31:         '
1  32:         'uxElementHost1
1  33:         '
1  34:         Me.uxElementHost.Dock = System.Windows.Forms.DockStyle.Fill
1  35:         Me.uxElementHost.Location = New System.Drawing.Point(0, 0)
1  36:         Me.uxElementHost.Name = "uxElementHost"
1  37:         Me.uxElementHost.Size = New System.Drawing.Size(222, 72)
1  38:         Me.uxElementHost.TabIndex = 0
1  39:         Me.uxElementHost.Text = "uxElementHost"
1  40:         Me.uxElementHost.Child = Me.uxWitWpfCustomControl
1  41:         '
1  42:         'WitCustomHostCOntrol
1  43:         '
1  44:         Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
1  45:         Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
1  46:         Me.Controls.Add(Me.uxElementHost)
1  47:         Me.Name = "WitCustomHostCOntrol"
1  48:         Me.Size = New System.Drawing.Size(222, 72)
1  49:         Me.ResumeLayout(False)
1  50: 
1  51:     End Sub
1  52:     Friend WithEvents uxElementHost As System.Windows.Forms.Integration.ElementHost
1  53:     Friend WithEvents uxWitWpfCustomControl As TWitWpfCustomControl
1  54: 
1  55: End Class

As you can see the only changes that have been made are to the class to add the generic type (line 5) and to the type used on the control instance (lines 29, 53).

note: Once you have made these and the following changes to the designer, you will no longer be able to view the designer in VS because we have made modifications for the designer.

  Creating a WPF Work Item Control  

Now we have changed the designer, we need to move on to the main control code and change it to pass all calls and implementation of the IWorkItemControl interface to the WPF control.

[ Get WitCustomControlBase.vb source  ] [ Get full source  ] [ Get latest full source  ]

1   1: Imports Microsoft.TeamFoundation.WorkItemTracking.Client
1   2: Imports Microsoft.TeamFoundation.WorkItemTracking.Controls
1   3: 'Imports Microsoft.VisualStudio.TeamFoundation.WorkItemTracking
1   4: 
1   5: 
1   6: Public Class WitCustomControlBase(Of TWitWpfCustomControl As {New, UIElement, IWorkItemControl})
1   7:     Implements IWorkItemControl
1   8: 
1   9: #Region " IWorkItemControl "
1  10: 
1  11:     Public Event AfterUpdateDatasource(ByVal sender As Object, ByVal e As System.EventArgs) Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.AfterUpdateDatasource
1  12:     Public Event BeforeUpdateDatasource(ByVal sender As Object, ByVal e As System.EventArgs) Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.BeforeUpdateDatasource
1  13: 
1  14:     Public Property [ReadOnly]() As Boolean Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.ReadOnly
1  15:         Get
1  16:             Return Me.uxWitWpfCustomControl.[ReadOnly]
1  17:         End Get
1  18:         Set(ByVal value As Boolean)
1  19:             Me.uxWitWpfCustomControl.[ReadOnly] = value
1  20:         End Set
1  21:     End Property
1  22: 
1  23:     Public Sub SetSite(ByVal serviceProvider As System.IServiceProvider) Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.SetSite
1  24:         Me.uxWitWpfCustomControl.SetSite(serviceProvider)
1  25:     End Sub
1  26: 
1  27:     Public Property WorkItemDatasource() As Object Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.WorkItemDatasource
1  28:         Get
1  29:             Return Me.uxWitWpfCustomControl.WorkItemDatasource
1  30:         End Get
1  31:         Set(ByVal value As Object)
1  32:             Me.uxWitWpfCustomControl.WorkItemDatasource = value
1  33:         End Set
1  34:     End Property
1  35: 
1  36:     Public Property WorkItemFieldName() As String Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.WorkItemFieldName
1  37:         Get
1  38:             Return Me.uxWitWpfCustomControl.WorkItemFieldName
1  39:         End Get
1  40:         Set(ByVal value As String)
1  41:             Me.uxWitWpfCustomControl.WorkItemFieldName = value
1  42:         End Set
1  43:     End Property
1  44: 
1  45:     Public Property Properties() As System.Collections.Specialized.StringDictionary Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.Properties
1  46:         Get
1  47:             Return Me.uxWitWpfCustomControl.Properties
1  48:         End Get
1  49:         Set(ByVal value As System.Collections.Specialized.StringDictionary)
1  50:             Me.uxWitWpfCustomControl.Properties = value
1  51:         End Set
1  52:     End Property
1  53: 
1  54:     Public Sub Clear() Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.Clear
1  55:         Me.uxWitWpfCustomControl.Clear()
1  56:     End Sub
1  57: 
1  58:     Public Sub FlushToDatasource() Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.FlushToDatasource
1  59:         Me.uxWitWpfCustomControl.FlushToDatasource()
1  60:     End Sub
1  61: 
1  62:     Public Sub InvalidateDatasource() Implements Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl.InvalidateDatasource
1  63:         Me.uxWitWpfCustomControl.InvalidateDatasource()
1  64:     End Sub
1  65: 
1  66: #End Region
1  67: 
1  68:     Private Sub uxWitCustomControl_AfterUpdateDatasource(ByVal sender As Object, ByVal e As System.EventArgs) Handles uxWitWpfCustomControl.AfterUpdateDatasource
1  69:         RaiseEvent AfterUpdateDatasource(sender, e)
1  70:     End Sub
1  71: 
1  72:     Private Sub uxWitCustomControl_BeforeUpdateDatasource(ByVal sender As Object, ByVal e As System.EventArgs) Handles uxWitWpfCustomControl.BeforeUpdateDatasource
1  73:         RaiseEvent BeforeUpdateDatasource(sender, e)
1  74:     End Sub
1  75: 
1  76: End Class

This control implements the IWorkItemControl and it is the control that Work Item Tracking loads. Work Item Tracking form display module does not know anything about WPF and we are just faking up the interface to allow us to use the full functionality on WPF in place of Windows Forms. We can use the same method to implement the other features like IWorkItemUserAction.

We then need a Work Item Custom Control (WICC) definition file.

[ Get SignOffControl.wicc source  ] [ Get full source  ] [ Get latest full source  ]

1   1: <?xml version="1.0"?>
1   2: <CustomControl xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
1   3:  <Assembly>Hinshelwood.WitCustomControls.dll</Assembly>
1   4:  <FullClassName>Hinshelwood.WitCustomControls.SignOff.SignOffHostControl</FullClassName>
1   5: </CustomControl>

This is the file that tells Visual Studio what to do.

This file and the .dll need to copied to the …Application DataMicrosoftTeam FoundationWork Item TrackingCustom Controls9.0 folder.

We can now add this control to a work item by modifying the XML definition of a Work item. To test I have just replaced an existing control.

You can add this using the Power Tools process template editor.

Creating a WPF Work Item Control  

The result?

Creating a WPF Work Item Control  

You will notice that this control is marked as read-only, but not bad for a first pass…

Technorati Tags: ALM    WPF    CodeProject    TFS 

Windows Software Development Azure DevOps Technical Mastery Internal Developer Platform
Comments

Related blog posts

No related videos found.

Connect with Martin Hinshelwood

If you've made it this far, it's worth connecting with our principal consultant and coach, Martin Hinshelwood, for a 30-minute 'ask me anything' call.

Our Happy Clients​

We partner with businesses across diverse industries, including finance, insurance, healthcare, pharmaceuticals, technology, engineering, transportation, hospitality, entertainment, legal, government, and military sectors.​

ProgramUtvikling Logo
Illumina Logo
Lockheed Martin Logo
Kongsberg Maritime Logo
Qualco Logo
SuperControl Logo
Alignment Healthcare Logo
Cognizant Microsoft Business Group (MBG) Logo

NIT A/S

Teleplan Logo
Microsoft Logo
Graham & Brown Logo
Lean SA Logo
Flowmaster (a Mentor Graphics Company) Logo
Brandes Investment Partners L.P. Logo
Philips Logo
Emerson Process Management Logo

CR2

Nottingham County Council Logo
Ghana Police Service Logo
Department of Work and Pensions (UK) Logo
Royal Air Force Logo
New Hampshire Supreme Court Logo
Washington Department of Transport Logo
Sage Logo
Lean SA Logo
Graham & Brown Logo
SuperControl Logo
Alignment Healthcare Logo
Freadom Logo