Click to Rate and Give Feedback
Related Articles

This month Advanced Basics flaunts the power of generics and reflection and shows how you get more flexible and efficient development by combining the two.

Ken Getz

MSDN Magazine January 2008

...

Read more!

Introducing Web-centric features of Windows Communication Foundation in the .NET Framework 3.5, including the HTTP programming model and the new syndication API.

Justin Smith

MSDN Magazine January 2008

...

Read more!

Dan Griffin shows the extensibility of Visual Studio 2005 Team Edition for Software Testers by discussing the modification of the existing Test Interface Provider sample in the latest Visual Studio SDK and implements Fuzz Testing.

Dan Griffin

MSDN Magazine November 2007

...

Read more!

This month: memory access issues in multi-core systems and diagnosing and avoiding false sharing in your parallel computing applications.

Stephen Toub, Igor Ostrovsky, and Huseyin Yildiz

MSDN Magazine October 2008

...

Read more!

To implement Silverlight in ASP.NET pages, you can encapsulate your Silverlight elements in ASP.NET controls. Here's how.

Fritz Onion

MSDN Magazine January 2008

...

Read more!

Also by this Author

Use the Team Foundation Server EventService to create and manage event subscriptions or create a Web service to receive and process events.

Brian A. Randell

MSDN Magazine May 2008

...

Read more!

Microsoft Visual Studio Tools for the Microsoft Office System is a new technology that brings the advanced features of Visual Studio .NET and the .NET Framework to apps built on Microsoft Word 2003 and Excel 2003. Now you can use Visual Basic .NET and C# to write document-centric, managed code solutions that run in-process with Word 2003 or Excel 2003, taking advantage of the rich object models they expose. Along the way you get the benefits of the managed environment in which a fully compiled .NET-based application executes, including code access ...

Read more!

The Express Editions of Visual Basic and SQL Server 2005 have lots of the features of the full-sized versions, but with a lot less of the overhead. Professional developer features such as full IntelliSense support, local debugger, Add Web Reference, and the improved Visual Data Tools will all be available in the Express products, so you don't have to leave your favorite features behind. In this article the author introduces you to these express editions and builds a sample app to get you started.

Brian A. Randell

MSDN ...

Read more!

Microsoft is introducing a new suite of tools (code-named "Whitehorse") that will make it easier for you to design and implement systems that conform to a service-oriented architecture. Two of these tools -- the SOA Design Suite and the Class Designer -- support the graphical design of systems and components with support for code generation and support for bi-directional synchronization which lets you ensure that your diagram always represents your system design. This article introduces these tools and shows you how they'll improve your design ...

Read more!

In this column, Brian Randell explains how to build a simple Work Item explorer and demonstrates the core operations needed to add work item support when building your own add-in.

Brian A. Randell

MSDN Magazine April 2007

...

Read more!

Popular Articles

James Avery does it again with his popular list of developer tools. This time he covers the best Visual Studio add-ins available today that you can download for free.

James Avery

MSDN Magazine December 2005

...

Read more!

Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation.

Josh Smith

MSDN Magazine July 2008

...

Read more!

Jeff Prosise explains when it's better to use UpdatePanel and when it's better to use asynchronous calls to WebMethods or page methods instead.

Jeff Prosise

MSDN Magazine June 2007

...

Read more!

We introduce you to the benefits of building composite applications with the Composite Application Guidance for WPF from Microsoft patterns & practices.

Glenn Block

MSDN Magazine September 2008

...

Read more!

Chris Tavares explains how the ASP.NET MVC Framework's Model View Controller pattern helps you build flexible, easily tested Web applications.

Chris Tavares

MSDN Magazine March 2008

...

Read more!

Team System
Work Items and Undo Support
Brian A. Randell

Code download available at: TeamSystem2007_09.exe (197 KB)
Browse the Code Online
In the January 2007 installment of this column (msdn.microsoft.com/msdnmag/issues/07/01/TeamSystem), I described how to build a Microsoft® Word 2003 add-in to work with the Team Foundation Server version control subsystem. In the April 2007 column (msdn.microsoft.com/msdnmag/issues/07/04/TeamSystem), I drilled down into the work item tracking subsystem. In this month's column, I'll describe how you can add support for work items to the add-in. In addition, you'll learn how to add a feature that should have been in the first version of the add-in—undo support.

Changes
In the first column describing the add-in, I used the beta release of Microsoft Visual Studio® 2005 Tools for the 2007 Microsoft Office system (Visual Studio 2005 Tools for Office Second Edition, or VSTO 2005 SE for short). Since that time, Microsoft has released the final version, which supports building application add-ins for both Office 2003 and the 2007 Office system. Thus, if you're working along with the article, you need to upgrade the first "release" of the add-in to the RTM version. To do this, simply open the solution and recompile on a machine with VSTO 2005 SE installed. Once you've verified the new version works, the next step is to add undo pending changes support. This feature requires you modify the tfsvcUtil class and the ThisAddin class.
In tfsvcUtil, add a new shared method UndoPendingChanges that accepts the full path to the document currently being modified, and have it return a Boolean. It turns out the core functionality of this new method mimics the existing CheckInDocument method. The method checks to ensure a valid connection has been made to the Team Foundation Server and that a valid workspace is loaded. Once this is accomplished, it gets an array of PendingChanges objects for the passed-in document. Assuming a pending change is returned, the Undo method of the user's workspace is called, passing in the PendingChanges array. This method should return an integer value of 1. If it does, the method returns true, otherwise a tfsUtilException is thrown using the newly defined MSG_UNDO_NOT_ZERO constant. Figure 1 provides the full method listing.
Public Shared Function UndoPendingChanges( _
  ByVal docPath As String) As Boolean
  If serverValidated Then
    If m_userWorkspace Is Nothing Then
      Dim userWorkstation As Workstation = Workstation.Current
      Dim docWSInfo As WorkspaceInfo = _
        userWorkstation.GetLocalWorkspaceInfo(docPath)
      m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo)
    End If

    Dim pc As PendingChange() = _
      m_userWorkspace.GetPendingChanges(docPath)

    If pc IsNot Nothing AndAlso pc.Length > 0 Then
      Dim retval As Integer = m_userWorkspace.Undo(pc)
      ' Retval should equal the number of items 'undone'
      If retval = 1 Then
        Return True
      Else
        Throw New tfsUtilException(String.Format( _
          MSG_UNDO_NOT_ZERO, docPath, retval))
      End If
    Else
      Return False
    End If
  Else
    Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED)
  End If
End Function

The changes you need to make to the ThisAddIn class are pretty straightforward. As before, due to space restrictions, I won't go into any great detail on the Word add-in specific code (you can compare this column's download to the previous version to see the changes). Needless to say, you will have to modify the class to add a new Undo Pending Changes button to the toolbar, add a click handler for the new button, and add additional code to handle state changes to control whether the button is enabled. Basically, the Undo Pending Changes button's enabled state should mirror that of the existing Check-in button.

Work Item Support
Adding support for associating work items with a check-in requires three major sets of changes to take place. First, you need to modify the tfsvcUtil class to support connecting to the work item store. Second, you need to modify the CheckInDocument method to do the actual association of work items. Finally, you need to modify the existing frmCheckIn dialog to support listing work items for the user to select as part of check-in.
To program work items, you add a reference to the Microsoft.TeamFoundation.WorkItemTracking.Client.dll assembly from the TFSUtil project. As mentioned in earlier columns, Team Explorer installs its supporting assemblies in the Global Assembly Cache (GAC) by default. However, the installer does not register them to show up in the Visual Studio 2005 Add References dialog. You'll need to either modify your registry so the Add References dialog sees the assemblies or manually browse for the assemblies. You'll find the assemblies at %Program Files%\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\.
Once you've added the reference, you need to modify the tfsvcUtil class and add an imports statement at the top of the source file as follows:
Imports Microsoft.TeamFoundation.WorkItemTracking.Client
Currently when the add-in connects to the Team Foundation Server box, it only connects to the version control service. You could modify the existing connect method to also connect to the work item store. However, there might be times when the user of the add-in is not going to associate work items, and thus this would add unnecessary overhead to the connect method. Instead, you'll add a new method that is called separately to connect to the work item store. You'll make this method public; however, most of the time existing methods will call the method before doing any work item-related operations. If the connection to the work item store is already in place, then it effectively becomes a no-op.
You need to add a new shared method ConnectWIS to the tfsvcUtil class. This method accepts no parameters and returns nothing. The method body is simple. First, it checks to make sure a valid Team Foundation Server instance exists. If not, it throws an exception since the user needs to initiate the connection before this method is called. That said, you could modify the architecture of the add-in to support attempting to login to the user's default Team Foundation Server installation. If the server is available, a connection is made to the work item store using the GetServiceMethod of the m_tfs instance, typed as a TeamFoundationServer object:
Public Shared Sub ConnectWIS()
  If Not m_tfs Is Nothing Then
    If tfsvcUtil.m_tfsWIS Is Nothing Then
      tfsvcUtil.m_tfsWIS = CType( _
        m_tfs.GetService(GetType(WorkItemStore)), WorkItemStore)
    End If
  Else
    Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED)
  End If
End Sub
You'll note the work item store reference is cached in a class-level variable m_tfsWIS, typed as a WorkItemStore object. You'll need to add this variable to the class like this:
Private Shared m_tfsWIS As WorkItemStore = Nothing
Once you've done this, you need to create an overloaded version of the CheckInDocument method so that it matches the existing version with an additional input parameter that accepts an array of WorkItemCheckinInfo instances. Next, cut the body from the existing method and paste it into your new method. Modify the original method so that it calls the new version, passing Nothing for the WorkItemCheckinInfo array. The modified version is now one line of code:
Return tfsvcUtil.CheckInDocument(docPath, comment, Nothing)
Now you need to modify the code in CheckInDocument to see if any WorkItemCheckinInfo objects were passed into the method. If this is the case, the code calls the ConnectWIS method to ensure a connection to the work item store has been made. Once you've done that, you call the overloaded version of CheckIn that accepts an array of WorkItemCheckinInfo instances, passing Nothing for check-in notes and policy overrides. Figure 2 provides the complete listing.
Public Shared Function CheckInDocument( _
  ByVal docPath As String, ByVal comment As String, _
  ByVal SelectedWorkItems() As WorkItemCheckinInfo) _
  As Integer

  If serverValidated Then
    If m_userWorkspace Is Nothing Then
      Dim userWorkstation As Workstation = Workstation.Current
      Dim docWSInfo As WorkspaceInfo = _
        userWorkstation.GetLocalWorkspaceInfo(docPath)
      m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo)
    End If

    Dim pc As PendingChange() = _
      m_userWorkspace.GetPendingChanges(docPath)

    If pc IsNot Nothing AndAlso pc.Length > 0 Then
      If SelectedWorkItems IsNot Nothing _
        AndAlso SelectedWorkItems.Length > 0 Then

        tfsvcUtil.ConnectWIS()

        Return m_userWorkspace.CheckIn( _
           pc, comment, Nothing, SelectedWorkItems, Nothing)
       Else
         Return m_userWorkspace.CheckIn(pc, comment)
      End If
    Else
      Return -1
    End If
  Else
    Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED)
  End If
End Function


Returning Work Item Queries
At this point, you've added work item association support to the check-in process. However, you need to enhance the tfsvcUtil class to support returning a list of work item queries for the current Team Project. In addition, you'll need a method that runs a query and returns the work items to the caller so that they can be displayed to the user as part of the integrated check-in experience.
To do this, add two methods. The first method, GetWIQ, returns a StoredQueriesCollection. The second method, RunWIQ, accepts a query name as string parameter and returns a WorkItemsCollection object. I detailed how to do this in the April 2007 edition of this column (msdn.microsoft.com/msdnmag/issues/07/04/TeamSystem). Figure 3 provides the code you need.
Public Shared Function GetStoredQueries(ByVal docPath As String) _
  As StoredQueryCollection

  If IsServerReady Then
    tfsvcUtil.ConnectWIS()

    If m_userWorkspace Is Nothing Then
      Dim userWorkstation As Workstation = Workstation.Current
      Dim docWSInfo As WorkspaceInfo = _
        userWorkstation.GetLocalWorkspaceInfo(docPath)
      m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo)
    End If

    Dim vcTeamProject As TeamProject = _
      m_userWorkspace.GetTeamProjectForLocalPath(docPath)

    m_wisProject = m_tfsWIS.Projects(vcTeamProject.Name)

    Return m_wisProject.StoredQueries()
  Else
    Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED)
  End If
End Function

Public Shared Function RunWIQ(ByVal docPath As String, _
  ByVal WIQ As StoredQuery) As WorkItemCollection

  Dim wiqlToExecute As String = WIQ.QueryText.ToLower()
  Dim params As Hashtable = Nothing
  Dim wic As WorkItemCollection = Nothing

  If wiqlToExecute.Contains(MACRO_PROJECT) Then
    If params Is Nothing Then
      params = New Hashtable
    End If
    params.Add(MACRO_PROJECT.Substring(1), WIQ.Project.Name)
  End If
  If wiqlToExecute.Contains(MACRO_ME) Then
    If params Is Nothing Then params = New Hashtable
    params.Add(MACRO_ME.Substring(1), _
      m_tfsVCS.AuthenticatedUser.Substring( _
      m_tfsVCS.AuthenticatedUser.IndexOf("\"c) + 1))
  End If

  If params IsNot Nothing Then
    wic = m_tfsWIS.Query(wiqlToExecute, params)
  Else
    wic = m_tfsWIS.Query(wiqlToExecute)
  End If

  If wic.Count = 0 Then
    Return Nothing
  Else
    Return wic
  End If
End Function


Updating the Check-In Dialog
Now that you've got the main code written to handle associating work items at check-in, you need to modify the existing check-in dialog to support work item association. Start by making the existing form larger, adjusting the size property to something like 640×480 to give you room to adjust the layout. Next, add a panel control to the form and name it pnlComments. Then cut the existing textbox txtComment from the form and paste it into the new panel, setting the Dock property to Fill. You'll need to re-add a handles clause to the txtComment_TextChanged event handler because, when you cut the textbox, the existing clause is removed.
You'll need a GroupBox control called grpOptions added to the form on the left. Add two RadioButton controls into the group box and name them rdoComments and rdoWorkItems. Change their AutoSize property to False and their Appearance property to Button. Set the rdoComments radio button's Checked property to True. Finally, rearrange the form so that it resembles Figure 4.
Figure 4 Modified Check-In Dialog with Comments Page (Click the image for a larger view)
With the basic UI changes in place, you need to add support for showing work items in a grid when the Work Items toggle button is clicked. Add a panel control to the form called pnlWorkItems. Inside that panel, add two panels: pnlSQCombo and pnlGridHolder. Set pnlSQCombo's Dock property to Top and pnlGridHolder's to Fill. You then need to add a label and a combobox control to pnlSQCombo and a DataGridView control to pnlGridHolder. Name the combobox cboStoredQueries and the grid dgvWorkItems. Dock the grid within the panel control using the smart tag. Adjust the size and location of pnlWorkItems to match pnlComments and set its Visible property to False.
When the user clicks the Work Items toggle button, the click event handler will perform the work necessary to make the work items available. There's no point in adding the extra overhead to the form load time unless you know you'll always want work items associated with a document check-in. There's quite a bit of code that makes things happen, much of which is generic Windows® Forms code necessary to set the UI correctly.
The order of operations when the Work Items toggle button is clicked is as follows: load the check-in action combobox data sources, define the grid's columns, load the stored queries, and then run the My Work Items query, binding the results of the query to the grid control. Figure 5 lists the code that gets called when the user clicks the Work Items toggle button.
Private Sub rdoWorkItems_Click(ByVal sender As Object, _
  ByVal e As System.EventArgs) Handles rdoWorkItems.Click
  ' Load ComboBox Data Sources
  LoadCheckInActionComboBox()

  ' Define Grid Layout
  DefineWorkItemGrid()

  ' Load WIQs
  LoadStoredQueries()
  For Each sq As StoredQuery In msq
    If sq.Name = "My Work Items" Then
      Me.cboStoredQueries.SelectedItem = sq
      Exit For
    End If
  Next

  ' Run WIQ
  LoadGridData()

  Me.pnlComments.Visible = False
  Me.pnlWorkItems.Visible = True
End Sub

The rdoWorkItems_Click event handler calls a number of supporting procedures to do its work. LoadCheckInActionComboBox initializes the data sources for the check-in action combobox that will appear in the grid. If you examine the Microsoft check-in dialog, you'll note that, depending upon the type of work item being selected, the check-in action combobox will either show only the word Associate or it will show Associate and Resolve. Mimicking this behavior requires a bit of behind-the-scenes work in the form.
In order to make this work, your first step is to define two class-level arrays:
Private listAssociate(0) As String
Private listAssociateResolve(1) As String
The next thing you'll do is swap the check-in action combobox's data source between these two arrays depending upon the type of work item selected. LoadCheckInActionComboBox loads the string values into the arrays.
The event handler then runs DefineWorkItemGrid to initialize the grid control. This method adds an unbound checkbox control, four data-bound text columns (Work Item Type, Work Item ID, Title, and State), and an unbound combobox control for the check-in action. Next, rdoWorkItems_Click calls LoadStoredQueries. This method calls the GetStoredQueries method you created earlier. It then data binds the results to the cboStoredQueries combobox control.
Back in the rdoWorkItems_Click method, the code walks the list of stored queries until it finds the My Work Items query. Once it finds this, the code sets that to be the currently selected query in the cboStoredQueries combobox control. Then the method executes LoadGridData, which retrieves the work items returned by the My Work Items query and binds the results to the grid. Finally, rdoWorkItems_Click hides the comments panel and shows the work items panel.
To make the grid work in a similar fashion to the Microsoft version, you need to write event handlers for two grid-related events. The first event is the CellFormatting. One of the columns displayed by the grid is the Type property from the WorkItem object. This property is typed as WorkItemType object. You want to display the Name property of this object in the grid. Unfortunately, when the ToString method is executed, the object returns its fully qualified type name, not its Name property. To display the Name property, you need to change the cell's value in the CellFormatting event handler. This code checks to see whether the column to format is the Work Item Type column:
Private Sub dgvWorkItems_CellFormatting(ByVal sender As Object, _
  ByVal e As DataGridViewCellFormattingEventArgs) _
  Handles dgvWorkItems.CellFormatting

  If e.ColumnIndex = typeColumnIndex Then
    If e.Value IsNot Nothing Then
      Dim wit As WorkItemType = CType(e.Value, WorkItemType)
      e.Value = wit.Name
      e.FormattingApplied = True
    End If
  End If
End Sub
If it is and the cell's value is not null, the code retrieves the WorkItemType instance from the cell and changes the value of the cell to the value of the Name property.
Next, you need to implement an event handler for the Current-CellDirtyStateChanged event. In this handler you're changing the data source of the check-in action combobox based upon the type of work item selected. Write the code so that it only executes when the checkbox column's state is changed.
To know what the data source should be, you need to access the WorkItem object that is bound to the current row. Once you have it, you call its GetNextState method passing in a string value of Microsoft.VSTS.Actions.Checkin. The code is checking to see if there's a valid state transition from a check-in. If there is, the code receives a valid string back that tells it that the combobox should display both Associate and Resolve. If a valid string is not retrieved, then only Associate should be available. In this way, the code sets the correct data source and then commits the current edit, changing the state of the checkbox.
The code then checks the value of the checkbox and, if it has been set to checked (True), the check-in action combobox is enabled and made the active control, and an edit is started. If the checkbox is returning to an unchecked state, the code resets the combobox. Figure 6 provides the details.
Private Sub dgvWorkItems_CurrentCellDirtyStateChanged( _
  ByVal sender As Object, ByVal e As System.EventArgs) _
  Handles dgvWorkItems.CurrentCellDirtyStateChanged

  Dim c As DataGridViewCell = dgvWorkItems.CurrentCell

  If c.ColumnIndex = checkedColumnIndex Then
    Dim dcb As DataGridViewComboBoxCell = _
      CType(dgvWorkItems(comboBoxColumnIndex, _
      c.RowIndex), DataGridViewComboBoxCell)

    Dim currentRow As DataGridViewRow = dgvWorkItems.Rows(c.RowIndex)
    Dim wi As WorkItem = CType(currentRow.DataBoundItem, WorkItem)

    Dim nextState As String = _
      wi.GetNextState("Microsoft.VSTS.Actions.Checkin")

    If String.IsNullOrEmpty(nextState) Then
      dcb.DataSource = listAssociate
    Else
      dcb.DataSource = listAssociateResolve
    End If

    dgvWorkItems.CommitEdit( _
      DataGridViewDataErrorContexts.CurrentCellChange)

    If CBool(c.Value) Then
      dcb.ReadOnly = False
      dcb.Value = dcb.Items.Item(0)

      dgvWorkItems.CurrentCell = dcb
      dgvWorkItems.BeginEdit(True)
    Else
      dcb.Value = Nothing
      dcb.ReadOnly = True
    End If
  End If
End Sub

The last bit of code you need to add to process work items is developed by creating an array of WorkItemCheckinInfo objects in the FormClosing event (see Figure 7). You create this array by walking the rows of the grid, creating a WorkItemCheckinInfo instance if the checkbox column is checked. When you create a WorkItemCheckinInfo instance, you specify the check-in action. In addition to this method, you need to add a public property, WorkItems, to the form so that the array can be retrieved once the dialog is closed. You set this property at the end of the FormClosing method.
Private Sub frmCheckIn_FormClosing( _
  ByVal sender As Object, ByVal e As FormClosingEventArgs) _
  Handles Me.FormClosing

  Dim chkCol As DataGridViewCell
  Dim cboCol As DataGridViewCell

  Dim wiciList As New List(Of WorkItemCheckinInfo)
  Dim cia As WorkItemCheckinAction = WorkItemCheckinAction.None

  For Each row As DataGridViewRow In Me.dgvWorkItems.Rows
    chkCol = row.Cells(checkedColumnIndex)
    If CBool(chkCol.Value) Then
      ' Item is selected
      cboCol = row.Cells(comboBoxColumnIndex)
      Select Case cboCol.Value.ToString()
        Case "Associate"
          cia = WorkItemCheckinAction.Associate
        Case "Resolve"
          cia = WorkItemCheckinAction.Resolve
        Case Else
          cia = WorkItemCheckinAction.None
      End Select

      wiciList.Add(New WorkItemCheckinInfo( _
        CType(row.DataBoundItem, WorkItem), cia))
    End If
  Next

  If wiciList.Count > 0 Then
    Me.WorkItems = wiciList.ToArray()
  Else
    Me.WorkItems = Nothing
  End If
End Sub


Final Details
You'll find some additional code in the form to handle the stored queries combobox's SelectedValueChanged event. The only thing left to do for the add-in to support work items is to modify the cbbCheckInDoc_Click method of the ThisAddIn class in the Word add-in. The modified version simply checks to see if the new WorkItems property of the dialog has data. If it does, the new version of the CheckInDocument method is called. Otherwise, the old version is executed. Figure 8 displays the completed check-in experience working with work items.
Figure 8 Completed Check-In Dialog with Work Item Support (Click the image for a larger view)
At this point, if you run the add-in, you can add a document to source control, check it in with comments and associate work items, check it out, and even undo pending changes. By this time, a lot of work has been accomplished, but there's even more that can be done. In the next edition of this column, I'll look at adding check-in notes, policy support, and possibly other interesting features.

Send your questions and comments to mmvsts@microsoft.com.


Brian A. Randell is a senior consultant with MCW Technologies LLC. Brian spends his time speaking, teaching, and writing about Microsoft technologies. He is the author of Pluralsight's Applied Team System course and is a Microsoft MVP. Contact Brian via his blog at mcwtech.com/cs/blogs/brianr.

Page view tracker