在本專欄 2007 年 1 月期中 (msdn.microsoft.com/msdnmag/issues/07/01/TeamSystem),我介紹了 如何創建 Microsoft® Word 2003 加載項來與 Team Foundation Server 版本控制子系統協同工作。 在 2007 年 4 月期的專欄中 (msdn.microsoft.com/msdnmag/issues/07/04/TeamSystem),我深入探討了 工作項目跟蹤子系統。在本月的專欄中,我將介紹如何向加載項添加工作項目支持。此外,您還將了解如 何添加原本應出現在第一版本加載項中的功能 — 撤消支持。
更改
在介紹加載項的第一期專欄中,我使用了 Microsoft Visual Studio® 2005 Tools for the 2007 Microsoft Office system 的測試版本(Visual Studio 2005 Tools for Office Second Edition,或簡稱為 VSTO 2005 SE)。從那之後,Microsoft 已經發布了最終版本,該版本支持為 Office 2003 和 2007 Office system 創建應用程序加載項。所以,如果您打算按照該專欄文章中的說明 操作,則需將該加載項的第一個“版本”升級到 RTM 版本。為此,只需在安裝了 VSTO 2005 SE 的計算機上打開該解決方案並重新編譯即可。一旦確認新版本可正常運行,下一步要做的就是添加撤 消掛起的更改支持。該功能要求您修改 tfsvcUtil 類和 ThisAddin 類。
在 tfsvcUtil 中,添加 一個新的共享方法 UndoPendingChanges(該方法可接受目前正在修改的文檔的完整路徑),並讓其返回 布爾值。事實證明,該新方法的核心功能很像現有的 CheckInDocument 方法。該方法會進行檢查,以確 保已與 Team Foundation Server 建立有效連接,並確認加載了有效工作區。這一步完成後,它將為傳入 的文檔獲取一個 PendingChanges 對象數組。假設返回了一項掛起的更改,然後調用用戶工作區的 Undo 方法,傳入 PendingChanges 數組。這一方法應返回整數值 1。如果確實如此,該方法返回 True,否則 便使用新定義的 MSG_UNDO_NOT_ZERO 常量引發 tfsUtilException。圖 1 提供了完整的方法列表。
Figure 1 tfsvcUtil 中的 UndoPendingChanges 方法
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
您需要對 ThisAddIn 類進行的更改非常簡單。像以前一樣,由於篇幅所限,我不 再對 Word 加載項特殊代碼進行更多詳述(您可以將本期專欄的下載內容和以前的版本做比較,看看有些 什麼變化)。毫無疑問,您必須修改該類,為工具欄添加新的“撤消掛起的更改”按鈕,為新 按鈕添加單擊處理程序,並添加其他代碼來處理狀態變化以控制該按鈕是否啟用。基本上,“撤消 掛起的更改”按鈕的啟用狀態應反映現有“簽入”按鈕的狀態。
工作項目支持
為將工作項目與簽入進行關聯添加支持需要進行三組重要更改。首先,需要修改 tfsvcUtil 類, 以支持與工作項目存儲區的連接。其次,需要修改 CheckInDocument 方法,以執行工作項目的實際關聯 。最後,需要修改現有的 frmCheckIn 對話框,以支持列出工作項目,以便用戶選擇其作為簽入的一部分 。
要對工作項目進行編程,可以從 TFSUtil 項目添加對 Microsoft.TeamFoundation.WorkItemTracking.Client.dll 程序集的引用。我在前幾期專欄文章中提到 過,Team Explorer 默認情況下會在全局程序集緩存 (GAC) 中安裝其支持程序集。然而,該安裝程序並 不注冊這些程序集,所以它們在 Visual Studio 2005“添加引用”對話框中不會顯示。您需 要修改注冊表以便讓“添加引用”對話框看到這些程序集,或者手動浏覽這些程序集。您可以 在 %Program Files%\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\ 中找到這些程序集 。
添加引用後,需要修改 tfsvcUtil 類,並在源文件的頂部添加一個 imports 語句,如下所示:
Imports Microsoft.TeamFoundation.WorkItemTracking.Client
目前,當加載項連接 到 Team Foundation Server 框時,它只連接版本控制服務。您可以修改現有的連接方法,以便也連接到 工作項目存儲區。但是,有時也會出現加載項用戶不打算關聯工作項目的情形,這樣就會為連接方法增加 不必要的系統開銷。您可以改為添加單獨調用的新方法來連接到工作項目存儲區。您可以公開該方法;但 是,在多數情況下,現有方法將在開始任何與工作項目相關的操作之前調用此方法。如果與工作項目存儲 區的連接已經到位,那麼該方法實際上成了 no-op。
需要向 tfsvcUtil 類添加一個新的共享方法 ConnectWIS。此方法不接受任何參數,也不返回任何內容。該方法的主體很簡單。首先,它會進行檢查以 確保存在有效的 Team Foundation Server 實例。如果沒有,它會引發異常,因為用戶需要在調用該方法 之前啟動連接。不過,您可以修改加載項的體系結構,以支持嘗試登錄到用戶的默認 Team Foundation Server 安裝。如果服務器可用,可以使用 m_tfs 實例的 GetServiceMethod 與工作項目存儲區建立連接 ,類型化為 TeamFoundationServer 對象:
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
您會注意到工作項目存儲區引用緩存在類級別變量 m_tfsWIS 中,類型化為 WorkItemStore 對象。需要將此變量添加到類中,如下所示:
Private Shared m_tfsWIS As WorkItemStore = Nothing
添加完畢後,需要創建一個重載版本的 CheckInDocument 方法,以 便它通過一個額外的輸入參數匹配現有版本,該參數接受 WorkItemCheckinInfo 實例的數組。接下來, 剪切現有方法的主體,然後將其粘貼到新方法中。修改原始方法,以使它調用新版本,為 WorkItemCheckinInfo 數組傳遞 Nothing。修改過的版本現在僅為一行代碼:
Return tfsvcUtil.CheckInDocument(docPath, comment, Nothing)
現在您需要修改 CheckInDocument 中的代碼,檢查是否有任何 WorkItemCheckinInfo 對象傳遞給該方法。如果有,該代碼會調用 ConnectWIS 方法,以確保已經建立與工作項目存儲區的連接。完成後,您可調用 CheckIn 的重載版本( 它可接受 WorkItemCheckinInfo 實例的數組),為簽入說明和策略覆蓋傳遞 Nothing。圖 2 提供了完整 列表。
Figure 2 帶有工作項目支持功能的簽入
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
返回工作項目查詢
此時,您已經為簽入過程添加了工作項目關聯支持。但 是您需要提升 tfsvcUtil 類,以支持返回針對當前“團隊項目”的工作項目查詢列表。此外 ,您還需要一個能運行查詢並向調用方返回工作項目的方法,這樣工作項目可以作為完整簽入體驗的一部 分向用戶顯示。
添加兩個方法便可以實現這一點。方法一:GetWIQ。它能返回 StoredQueriesCollection。方法二: RunWIQ。它接受查詢名稱作為字符串參數,返回一個 WorkItemsCollection 對象。我在本專欄 2007 年 4 月一期 (msdn.microsoft.com/msdnmag/issues/07/04/TeamSystem) 中詳細說明了操作方法。圖 3 提 供了所需的代碼。
Figure 3 GetStoredQueries 和 RunWIQ 方法
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
更新簽入對話框
既然您已編寫好主要代碼,可以在簽入中處理關聯工作項目了,您需要修改現 有簽入對話框來支持工作項目關聯。首先,擴大現有窗體,將大小屬性調整到 640×480 的樣子, 這樣您就有余地調整布局。接下來,向此窗體添加面板控件並將其命名為 pnlComments。然後,從該窗體 中剪切現有文本框 txtComment,將其粘貼到新的面板中,將 Dock 屬性設置為 Fill。需要向 txtComment_TextChanged 事件處理程序重新添加一個 handles 子句,因為現有子句在您剪切文本框時被 刪除了。
需要將名為 grpOptions 的 GroupBox 控件添加到左側的窗體。向分組框添加兩個 RadioButton 控件,分別命名為 rdoComments 和 rdoWorkItems。將它們的 AutoSize 屬性更改為 False ,將 Appearance 屬性改為 Button。將 rdoComments 單選按鈕的 Checked 屬性設置為 True。最後,重 新安排窗體,使其看起來就像圖 4 所示。
圖 4帶有注釋頁的 修改過的簽入對話框
在基本 UI 更改就緒後,您需要添加支持以在單擊“工作項目” 切換按鈕時能在網格中顯示工作項目。向稱為 pnlWorkItems 的窗體添加一個面板控件。在該面板內部添 加兩個面板:pnlSQCombo 和 pnlGridHolder。將 pnlSQCombo 的 Dock 屬性設置為 Top,將 pnlGridHolder 的屬性設為 Fill。然後,您需要向 pnlSQCombo 添加一個標簽和一個組合框控件,向 pnlGridHolder 添加一個 DataGridView 控件。將組合框命名為 cboStoredQueries,將網格命名為 dgvWorkItems。使用智能標記將網格停靠在面板控件中。調整 pnlWorkItems 的大小和位置,讓它們和 pnlComments 相匹配,並將其 Visible 屬性設置為 False。
當用戶單擊“工作項目” 切換按鈕時,單擊事件處理程序將執行必要的操作,使工作項目可用。除非您知道自己始終希望將工作項 目和文檔簽入相關聯,否則為窗體加載時間添加額外系統開銷毫無意義。有一些代碼能實現此目的,其中 大部分都是正確設置 UI 所需的通用 Windows® 窗體代碼。
當單擊“工作項目” 切換按鈕時,操作順序如下所示:加載簽入操作組合框數據源,定義網格列,加載存儲查詢,然後運行 My Work Items 查詢,將查詢結果綁定到網格控件。圖 5 列出了用戶單擊“工作項目”切換 按鈕時被調用的代碼。
Figure 5 rdoWorkItems_Click 事件處理程序
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
rdoWorkItems_Click 事件處理程序會調用許多支持程序來完成它的工作。 LoadCheckInActionComboBox 為將顯示在網格中的簽入操作組合框初始化數據源。如果檢查 Microsoft 簽入對話框,您將發現,根據選中的工作項目的類型,簽入操作組合框要麼只顯示 Associate 一個字眼 ,要麼顯示 Associate 和 Resolve。模仿這種行為需要在窗體中做一點幕後工作。
為了操作順利 ,您第一步要做的是定義兩個類級別數組:
Private listAssociate(0) As String Private listAssociateResolve(1) As String
接下來要做的是根據選擇的工作項目的類型,在 這兩個數組間交換簽入操作組合框的數據源。LoadCheckInActionComboBox 會將字符串值加載到數組中。
然後,事件處理程序會運行 DefineWorkItemGrid 將網格控件初始化。此方法會為簽入操作添加 一個未綁定的復選框控件、四個數據綁定文本列(工作項目類型、工作項目 ID、標題和狀態),以及一 個未綁定的組合框控件。接著,rdoWorkItems_Click 會調用 LoadStoredQueries。該方法會調用您先前 創建的 GetStoredQueries 方法。然後,它會將結果數據綁定到 cboStoredQueries 組合框控件。
而在 rdoWorkItems_Click 方法中,代碼會遍歷存儲查詢列表,直到找到 My Work Items 查詢。 一旦找到,該代碼就在 cboStoredQueries 組合框控件中將它設為當前選擇的查詢。然後,該方法會執行 LoadGridData,檢索由 My Work Items 查詢返回的工作項目,並將結果綁定到網格。最後, rdoWorkItems_Click 隱藏注釋面板,顯示工作項目面板。
為了讓網格以類似於 Microsoft 版本 的方式工作,您需要為兩個與網格相關的事件編寫事件處理程序。第一個事件是 CellFormatting。網格 顯示的其中一個列是來自 WorkItem 對象的 Type 屬性。該屬性被類型化為 WorkItemType 對象。您希望 在網格中顯示此對象的 Name 屬性。但遺憾的是,在執行 ToString 方法時,此對象返回的是其完全限定 的類型名稱,而不是其 Name 屬性。要顯示 Name 屬性,需要在 CellFormatting 事件處理程序中更改單 元格的值。本代碼會進行檢查,以確定待格式化的列是否為“工作項目類型”列:
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
如果的確是,而且單元格的值不為空,那麼該代碼就會從單元格中檢索 WorkItemType 實例,將單元格的值更改為 Name 屬性的值。
接下來,需要為 Current-CellDirtyStateChanged 事件實現一個事件處理程序。在此處理程序中,您要根據所選的工作項目的類型來更改簽入操作組合框的 數據源。編寫代碼以使它僅在復選框列的狀態更改時才執行。
要了解數據源應該是什麼,您需要 訪問綁定到當前行的 WorkItem 對象。獲得數據源以後,您可以調用它的 GetNextState 方法,傳入 Microsoft.VSTS.Actions.Checkin 的一個字符串值。該代碼將進行檢查,以核實是否存在來自簽入的有 效狀態轉換。如果存在,該代碼會收到一個有效字符串,指示組合框應顯示 Associate 和 Resolve 兩者 。如果沒有檢索到有效字符串,則只能顯示 Associate。這樣,該代碼就會設置正確的數據源,然後提交 當前編輯,更改復選框的狀態。
然後,該代碼會檢查復選框的值,如果已經被設置為選中 (True) ,簽入操作組合框就會被啟用,成為激活控件,編輯即開始。如果復選框返回到未選中狀態,該代碼會重 置組合框。圖 6 提供了相關的詳細信息。
Figure 6 完整的 CurrentCellDirtyStateChanged 事件處理程序
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
您為了處理工作項目而需要添加的最後一部分代碼是通過在 FormClosing 事件中創建 WorkItemCheckinInfo 對象數組而開發的(參見圖 7)。創建此數組的方法是遍歷網格行,創建 WorkItemCheckinInfo 實例(如果已選中復選框列)。創建 WorkItemCheckinInfo 實例時,指定簽入操 作。除此方法以外,您還需要將一個公共屬性 WorkItems 添加到窗體,以便在對話框關閉時可以檢索到 該數組。該屬性應在 FormClosing 方法的末尾設置。
Figure 7 frmCheckIn 對話框中的 FormClosing 事件處理程序
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
最後細節
您會在窗體中找到一些其他代碼,用來處理存儲查詢組合框的 SelectedValueChanged 事件。為支持 工作項目而為加載項所做的最後一步是在 Word 加載項中修改 ThisAddIn 類的 cbbCheckInDoc_Click 方 法。修改過的版本僅進行簡單檢查,確定對話框的新 WorkItems 屬性是否有數據。如果有,說明已經調 用新版本的 CheckInDocument 方法。否則,說明還在執行舊版本。圖 8 顯示了使用工作項目的已完成簽 入體驗。
圖 8 帶有工作項目支持功能的完成的簽入對話框
此時,如果您運行加載項,可以向源控件添加一個文檔、通過注釋和關聯工作項目將其簽入和簽出, 甚至撤消掛起的更改。到這一步,很多工作都已經完成,但仍有很多工作可做。在本專欄的下一期中,我 將介紹添加簽入說明和策略支持,也許還會談到其他有趣的功能。
請將您想詢問的問題和提出的意見發送至 [email protected].
本文配套源碼:http://www.bianceng.net/dotnet/201212/829.htm