在我的上一專欄中,我開始說明如何使用 Team System 中公開的 API 為 Microsoft® Word 2003 生成源代碼控制外接程序。如果在 Visual Studio® 2005 中檢查團隊資源管理器公開的簽入對話框 ,則會注意到集成的簽入體驗是相當豐富的。您不僅可以簽入源文件,而且可以使簽入與工作項關聯,添 加簽入注釋,以及根據策略驗證簽入。圖 1 顯示選中“工作項”選項時的標准簽入對話框。
圖 1 團隊 資源管理器集成的簽入對話框
從表面上看這是很簡單的,其實不然,簽入體驗的此部分展示了大 量工作項功能。例如,可以更改用於顯示工作項列表的查詢。可以深入查看特定的工作項,並使用標准的 工作項 UI 操作它(通過雙擊)。而且,可以執行簽入操作,用來使工作項與該簽入的變更集關聯或執行 另一操作(如將工作項標記為已完成)。
需要很好地了解工作項 API,然後才能在外接程序中實 現這些功能。在此專欄中,我將說明如何生成簡單的工作項資源管理器(參見圖 2)。此示例演示為外接 程序添加工作項支持所需的核心操作。由於篇幅限制,我將僅涉及訪問 Visual Studio 2005 Team Foundation Server 的工作項服務所需的核心代碼。我不會詳細說明該示例的與 Windows® 窗體相關 的代碼。(但是,在下載中提供了所有代碼。)在 Team System 專欄的下一部分中,我將說明如何為外 接程序添加工作項支持。
圖 2 簡單的工作項資源管理器
Team Foundation Server、團隊項目和工作項
工作項跟蹤子系統是 Team Foundation Server 安裝的核心功能。為了使用工作項,至少需要一個使用“新建團隊項目”向導創建的團隊項目 。使用此向導時,指定要使用的流程模板(描述您的團隊項目將基於的結構的藍圖)。
在此討論 中尤其要注意的是,所選的流程模板定義團隊項目最初支持的工作項的默認類型。可以選擇自定義現有的 工作項類型或添加自己的工作項類型。工作項為您的團隊成員提供了一種僅跟蹤與團隊項目相關的任何信 息(包括但不限於錯誤和任務)的方法。
默認的 Team Foundation Server 安裝包括兩個流程模 板:Microsoft Solutions Framework for Agile Software Development 4.0 版(簡稱 MSF Agile)和 MSF for CMMI Process Improvement 4.0 版(簡稱 MSF CMMI)。圖 3 概述了工作項的默認類型、每種 類型的用途以及包括每種類型的模板。
Figure 3 默認工作項類型
工作項類型 說明 MSF Agile MSF CMMI 錯誤 通知系統中可能存在或已存在問題。 ** ** 變 更請求 標識對產品或基准的某部分進行的提議更改。 ** 問題 說明可能會妨礙工作或當前正妨礙產品工作的事件或情形。 ** 服務質量要求 說明系統的特征,如性能、負載、 可用性、壓力、可訪問性、可服務性和可維護性。 ** 要求 捕獲和跟蹤為解決客戶問題產品需要執行的操作。 ** 檢查 說明設計或代碼檢查的結果。 ** 風險 標 識將來可能會對項目產生負面後果的任何可能事件或情況。 ** ** 方案 記錄整個系統中用戶交互的單個路徑。 ** 任務 通知需要執行某項工作。 ** **每個工作項都由一組 字段組成。Team Foundation Server 認為字段適用於整個服務器安裝。可以創建自己的自定義流程模板 ,從而從頭開始或通過修改 Microsoft 提供的模板來創建自己的自定義工作項。定義自己的自定義工作 項時,還可以定義自定義字段定義。然而,這是另一篇文章的討論內容。
進行連接
該外接程序當前通過 tfsvcUtil 類中的 Connect 方法連接到 Team Foundation Server 安裝及其版本控制服務。此外,它使用自定義的 Windows 窗體對話框從用戶處獲取服務器連接字 符串。可以使用該代碼並進行幾處小修改以包括與工作項跟蹤 (WIT) 存儲區的連接。首先,需要添加對 Microsoft.TeamFoundation.WorkItemTracking.Client.dll 程序集的引用。然後,在 tfsvcUtil 類中, 需要添加以下 Imports 語句:
Imports Microsoft.TeamFoundation.WorkItem- Tracking.Client
還必須提供定義為將引用保存到 WIT 存儲區的類級變量:
Private m_tfsWIT As WorkItemStore
最後,需要將另一行代碼添加到 Connect 方法,以便它可以連接到 WIT 存儲區(參見圖 4)。
Figure 4 連接到 WIT 存儲區
Public Shared Sub Connect( _ ByVal fqServerName As String, ByVal showLoginUI As Boolean) Try m_serverName = fqServerName If showLoginUI Then Dim icp As ICredentialsProvider = New UICredentialsProvider m_tfs = TeamFoundationServerFactory.GetServer( _ m_serverName, icp) Else m_tfs = TeamFoundationServerFactory.GetServer(m_serverName) End If m_tfs.Authenticate() ' Version Control Server m_tfsVCS = CType(m_tfs.GetService( _ GetType(VersionControlServer)), _ VersionControlServer) ' Work Item Store m_tfsWIT = CType(m_tfs.GetService( _ GetType(WorkItemStore)), WorkItemStore) serverValidated = True Catch ex As Exception Throw New tfsUtilException( _ String.Format(MSG_UNEXPECTED, "Connect"), ex, m_serverName) End Try End Sub
假定存在有效的 TeamFoundationServer 引用,只需調用 TeamFoundationServer 的 GetService 方法並傳遞 WorkItemStore 的類型對象,即可連接到工作項存儲區。如果更深入地研究 Team Foundation Server API,您將會發現 DomainProjectPicker 類 - 隱藏在 Microsoft.TeamFoundation.dll 程序集內 Proxy 命名空間中的精華。此類公開 GUI,這樣您就可以定義 Team Foundation Server 連接以及(可選)訪問在服務器上定義的團隊項目。示例應用程序使用此類而 不是上述代碼。示例應用程序需要對以下 Team Foundation Server 程序集的引用才能執行其任務:
Microsoft.TeamFoundation.dll
Microsoft.TeamFoundation.Client.dll
Microsof t.TeamFoundation.Common.Library.dll
Microsoft.TeamFoundation.WorkItemTracking.Client.d ll
Microsoft.TeamFoundation.WorkItemTracking.Controls.dll
示例應用程序的主窗體 (frmMain.vb) 包括六個按鈕、一個列出選定團隊項目的組合框、一個列出活動團隊項目的可用工作項類 型的組合框和幾個提供狀態信息的標簽。需要將以下 Imports 語句添加到 frmMain.vb 的頂部:
Imports Microsoft.TeamFoundation.Client Imports Microsoft.TeamFoundation.Common Imports Microsoft.TeamFoundation.Proxy Imports Microsoft.TeamFoundation.Server Imports Microsoft.TeamFoundation.WorkItemTracking.Client
還必須將代碼添加到“選 取 Team Foundation Server”按鈕中的 click 事件處理程序,如圖 5 所示(請注意,為節省篇幅 ,省略了與 GUI 管理相關的代碼)。此代碼顯示默認的“連接到 Team Foundation Server” 對話框。DomainProjectPicker 類的構造函數提供一個枚舉以控制對話框的樣式。例如,可以選擇顯示其 中團隊項目選擇處於關閉狀態的對話框。但是,對於此示例,默認行為是您所需要的。圖 6 顯示使用默 認構造函數對 ShowDialog 進行調用的結果。
Figure 5 使用 DomainProjectPicker 類進行連接
Private Sub btnPickTFS_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnPickTFS.Click Dim dpp As New DomainProjectPicker() Select Case dpp.ShowDialog(Me) Case Windows.Forms.DialogResult.OK mwis = Nothing mprj = Nothing mtfs = dpp.SelectedServer() Me.lblTFS.Text = mtfs.Name Me.cboTeamProjects.DataSource = dpp.SelectedProjects() Me.cboTeamProjects.DisplayMember = "Name" If dpp.SelectedProjects.Length > 0 Then Me.cboTeamProjects.Enabled = True Me.btnConnectWIS.Enabled = True End If End Select End Sub
圖 6“連接”對話框
單擊“服務器”按鈕時,該對話框顯示內置的“添 加/移除 Team Foundation Server”對話框。如果選擇一個或多個團隊項目,然後單擊“確定 ”,則代碼將選定項目的列表綁定到“團隊項目”組合框控件。
訪問 WIT 存儲 區
具有與服務器的有效連接後,可以連接到 WIT 存儲區。在“連接到工作項存儲區” 按鈕的 click 事件處理程序中,需要使用前面討論的 GetService 方法連接到 WIT 存儲區。在連接後, 代碼將使用當前選定的團隊項目名稱初始化類級項目引用:
If mtfs IsNot Nothing Then mwis = CType(mtfs.GetService( _ GetType(WorkItemStore)), WorkItemStore) End If mprj = mwis.Projects.Item(Me.cboTeamProjects.Text)
具有與 WIT 存儲區的連接後,可以從 服務器獲取信息。私有方法 UpdateInfoData(如圖 7 所示)執行此任務並在主窗體上顯示數據。代碼首 先將選定項目的工作項類型列表作為 WorkItemTypes 對象的數組獲取。它將數組綁定到“類型 ”組合框,然後使用 WIT 存儲區的 FieldDefinitions 集合獲取服務器的字段定義計數。接下來, 代碼檢索可用於連接到服務器的身份的存儲查詢(包括公共查詢和私有查詢)的計數。最後,代碼構造動 態工作項查詢語言 (WIQL) 查詢以獲取對連接的身份可用的工作項列表。(稍後提供有關 WIQL 的更多詳 細信息。)請注意,代碼通過 WIT 存儲區引用獲取字段定義列表和工作項計數,而它使用活動的團隊項 目引用檢索工作項類型和存儲查詢。
Figure 7 UpdateInfoData 列表
Private Sub UpdateInfoData() If mprj IsNot Nothing Then Me.cboWIType.DataSource = mprj.WorkItemTypes Me.cboWIType.DisplayMember = "Name" Me.cboWIType.Enabled = True Me.lblFieldCount.Text = mwis.FieldDefinitions.Count.ToString() Me.lblSQCount.Text = mprj.StoredQueries.Count.ToString() Dim wiql As String = String.Format( _ "SELECT [System.Id] FROM WorkItems " & vbCrLf & _ "WHERE [System.TeamProject] = '{0}'", mprj.Name) Me.lblWICount.Text = mwis.QueryCount(wiql).ToString() End If
WIQL 是一種類似 SQL 的語言。在 msdn2.microsoft.com/bb130155.aspx 上聯機提供 了完整的語法參考。可以將 WIQL 查詢作為動態 WIQL 語句執行或從存儲查詢執行。可以將查詢發布到 Team Foundation Server,使它們可作為公共查詢供整個團隊使用或者作為私有查詢僅供您自己使用。
有效查詢返回 WorkItem 的集合。WIT 僅返回與查詢請求匹配且對基於任何安全限制執行查詢的 身份可見的那些工作項。請注意,如果由於安全性而限制了結果集,則 WIT 不引發異常。每種 WorkItem 類型都具有 Field 對象的集合;而每個字段又映射到為該特定字段提供元數據的 FieldDefinition 對象 。
構造有效的 WIQL 查詢需要有效的字段名稱列表(使用字段名稱或其引用名稱)。字段名稱是 友好版本;如果它由多個詞組成,則通常包含空格。在 WIQL 查詢中使用時,需要將包含空格的字段名稱 用方括號括起來。引用名稱是字段的完全限定名稱,與用於 .NET 類型的格式類似。例如,工作項的 Title 使用友好名稱 Title 和引用名稱 System.Title。又例如,Integration Build 字段的名稱為 Integration Build,其引用名稱為 Microsoft.VSTS.Build.IntegrationBuild。它在 CMMI 模板 Change Request 工作項窗體上的標簽為 Integrated In。可以訪問有效 WorkItemStore 引用的 FieldDefinition 檢查特定服務器的所有字段定義。如果運行示例應用程序並單擊“顯示字段列表 ”,則可以查看所有已定義字段的列表,並深入查看每個項。可以使用此信息幫助構造 WIQL 查詢 ,或者甚至生成自己的查詢生成 UI。
使用查詢
默認 Team Foundation Server 安裝附帶 的兩個流程模板中的每個模板都包含一組預定義的 WIQL 查詢。MSF Agile 模板包含 11 個查詢,而 MSF CMMI 模板包含 16 個查詢。可基於每個項目訪問存儲查詢。單擊示例應用程序中的“顯示存儲查詢 ”按鈕可加載一個列出活動團隊項目的所有存儲查詢的窗體。窗體上的列表框按名稱顯示所有存儲 查詢。雙擊查詢將打開一個與圖 8 所示窗體類似的詳細信息窗體。如果檢查 Microsoft 提供的查詢,則 將看到在引用工作項字段時,它們使用引用名稱。
圖 8 所有任務查詢的存儲查詢詳細信息
詳細信息窗體包含兩個按鈕:一個用於執行查詢並在網格中返回結果,另一個用於在列表視圖中返回 結果。WIT API 支持執行 WIQL 查詢的許多方法。圖 8 所示的詳細信息窗體使用兩種不同的方法執行查 詢。如果單擊“執行查詢(網格)”按鈕,則代碼將准備 WIQL 語句並將它傳遞到實際執行查詢 的 frmWIList。單擊“執行查詢(列表)”按鈕時,將以不同方式准備查詢並使用存儲查詢的項 目存儲區引用的 Query 方法執行查詢(參見圖 9)。
Figure 9 執行 WIQL 查詢
Private Sub btnExecuteGrid_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnExecuteGrid.Click Dim wiqlToExecute As String = mSQ.QueryText.ToLower() If wiqlToExecute.Contains(MACRO_PROJECT) Then wiqlToExecute = wiqlToExecute.Replace( _ MACRO_PROJECT, "'" & mSQ.Project.Name & "'") End If Using f As New frmWIList(mSQ.Project.Store, wiqlToExecute) f.ShowDialog(Me) End Using End Sub Private Sub btnExecuteList_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles btnExecuteList.Click Dim wiqlToExecute As String = mSQ.QueryText.ToLower() Dim params As New Hashtable Dim wic As WorkItemCollection = Nothing If wiqlToExecute.Contains(MACRO_PROJECT) Then params.Add(MACRO_PROJECT.Substring(1), mSQ.Project.Name) wic = mSQ.Project.Store.Query(wiqlToExecute, params) Else wic = mSQ.Project.Store.Query(wiqlToExecute) End If If wic.Count = 0 Then MessageBox.Show("The query didn't return any Work Items.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) Else Dim wi As WorkItem = wic.Item(0) Dim d As New frmAWorkItem(Nothing) Dim title As String = _ String.Format("{0}'s Work Items: {1}", _ mSQ.Name, wic.Count) Using f As New frmListBox(title, wi, wic, "Title", d) f.ShowDialog(Me) End Using End If End Sub
這兩段代碼都通過檢查預定義的查詢宏來准備 WIQL:@project。(WIQL 語句中前綴為 @ 符號的任何字符串都被視為宏。)WIT 探測自動擴展某些內置宏,如 @today。您自己的宏和 @project 宏需要手動替換。
根據執行查詢的方式,可以讓 WIT API 來執行宏替換或者您自己來執行。 btnExecuteGrid 中的代碼僅查找 @project 宏來執行簡單的手動替換。但是,btnExecuteList 讓 WIT API 執行繁重的工作。對於運行代碼,建議使用 API,因為使用替換方法時您可能會無意中執行不正確的 替換(例如,如果要替換 @project,但是存在超級字符串,如 @projected)。
如果查看每次單 擊按鈕和使用 API 後的結果,則將看到您要查找的結果通常會強制您遵循特定的途徑。在示例應用程序 中,網格詳細信息窗體將僅支持 WIQL 字符串的 WorkItemResultGrid 控件(稍後將進行詳細介紹)用作 API 選擇的輸入限制。另一方面,列表視圖使用標准數據綁定將 WorkItemCollection 掛接到標准的 Windows 窗體 ListBox 控件。然而,最終結果是相同的 - 執行查詢將返回 WorkItem 對象的集合。
顯示和編輯工作項
如果可以執行 WIQL 查詢(動態執行或從存儲查詢執行),則能夠使用 工作項對象模型查看和操作工作項。但是,有時候可能需要顯示 UI,以便用戶可以輸入、編輯或查看工 作項信息。實際上,如果使用團隊資源管理器集成的簽入體驗執行簽入,則您將會發現雙擊工作項將彈出 標准的工作項編輯器。這正是您希望在要生成的外接程序中具有的功能。
Microsoft 已通過 Microsoft.TeamFoundation.WorkItemTracking.Controls.dll 程序集中的用戶控件公開了主要的工作項 體驗。示例應用程序使用以下三個控件:WorkItemResultGrid(單擊 frmStoredQueryDetails.vb 窗體上 的 btnExecuteGrid 按鈕時,它是可見的)、PickWorkItemsControl(用於執行存儲查詢和搜索)和 WorkItemFormControl(從網格視圖或列表視圖窗體雙擊工作項時,它是可見的)。WorkItemFormControl 控件如圖 10 所示。
圖 10 WorkItemFormControl
這三個控件在搜索、查看和編輯工作項時執行所有繁重的工作。盡管可以將控件添加到工具箱,但是 示例應用程序動態地設置控件。沒有任何真正的設計時體驗。在您單擊主窗體上的“搜索工作項”按鈕時 ,示例應用程序使用 PickWorkItemsControl。
該控件需要對有效 WIT 存儲區對象的引用和對活動項 目的引用。從此處開始執行所有艱辛的工作。承載控件的示例窗體掛接控件的 PickWorkItemsListViewDoubleClicked 事件,以使用 WorkItemFormControl 顯示工作項,如圖 10 所示 。
WorkItemFormControl 公開 Item 屬性(在其中設置應該顯示的活動工作項)。您具有對每個字段及 其已更新狀態的完全訪問權限。還可以通過使用 Reset 方法將工作項的值重置為其原始查詢值。但是, 請牢記您應負責實現自己的保存和取消邏輯。
圖 11 提供執行該操作所需的相關代碼段。請注意,如果用戶通過“取消”按鈕或者通過使用窗口修 飾來取消編輯操作,則代碼將重置工作項。如果使用 WorkItem 的集合,則這一點很重要。該控件獲取 UI 中的更改並立即將它們傳輸到工作項。如果不重置,則工作項處於已更新狀態,而且如果用戶重新打 開它,則它仍將具有該信息。
Figure 11 處理保存和取消
Imports Microsoft.TeamFoundation.WorkItemTracking.Client Imports Microsoft.TeamFoundation.WorkItemTracking.Controls Friend Class frmAWorkItem Implements IProcessDoubleClick Private IsNewWI As Boolean = False Private IsCanceled As Boolean = False Private wifCtl As WorkItemFormControl = Nothing Private Sub frmAWorkItem_FormClosing( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing If wifCtl.Item.IsDirty AndAlso (Not IsCanceled) Then Select Case MessageBox.Show( _ "You made changes to the work item." & vbCrLf & _ "Would you like to save your changes?", "Save Changes", _ MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question) Case Windows.Forms.DialogResult.Yes e.Cancel = (Not Me.Save()) Case Windows.Forms.DialogResult.No If wifCtl.Item.IsDirty OrElse _ (Not wifCtl.Item.IsValid) Then wifCtl.Item.Reset() End If Case Windows.Forms.DialogResult.Cancel e.Cancel = True End Select End If End Sub Private Sub btnCancel_Click( _ ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnCancel.Click If wifCtl.Item.IsDirty OrElse (Not wifCtl.Item.IsValid) Then wifCtl.Item.Reset() End If IsCanceled = True End Sub Private Sub btnSave_Click( _ ByVal sender As Object, ByVal e As System.EventArgs) _ Handles btnSave.Click If wifCtl.Item.IsDirty Then Me.Save() Me.btnSave.Enabled = False End If End Sub Private Function Save() As Boolean Dim badFields As ArrayList = wifCtl.Item.Validate() If Not wifCtl.Item.IsValid Then Using sw As New IO.StringWriter sw.WriteLine("The following fields are invalid: ") For Each f As Field In badFields If Not f.IsValid Then sw.WriteLine(f.Name) End If Next MessageBox.Show(sw.ToString(), "Invalid fields", _ MessageBoxButtons.OK, MessageBoxIcon.Error) MsgBox(sw.ToString()) End Using Return False End If wifCtl.Item.Save() If IsNewWI Then Me.Text = "Work Item: " & wifCtl.Item.Id MessageBox.Show(String.Format( _ "New {0} saved. It's ID Is {1}", wifCtl.Item.Type.Name, _ wifCtl.Item.Id), "Save Work Item", _ MessageBoxButtons.OK, MessageBoxIcon.Information) End If Return True End Function End Class
執行保存操作時,可以使用 IsDirty 和 IsValid 屬性確定保存是否必需和可能。如果工作項無效, 則使用工作項的 Validate 方法檢索無效字段的列表。這樣,您就有機會更正代碼中的錯誤或使用戶返回 到 UI(具有與圖 11 中的代碼相同的作用)。如果工作項有效,則使用 Save 方法保存對對象的更改並 返回到 Team Foundation Server。
最後,如果希望能夠在應用程序中創建新的工作項,則還需要一部分數據。作為在前面的圖 7 中所示 代碼的一部分,主窗體上的一個組合框與活動項目支持的工作項列表一起加載。組合框控件列出每個工作 項的名稱。這是您旋轉工作項和打開 UI 所真正需要的全部內容。以下代碼顯示您需要執行的操作:
Dim WIT As String = Me.cboWIType.Text If WIT IsNot Nothing AndAlso WIT <> String.Empty Then Dim wi As WorkItem = mprj.WorkItemTypes(WIT).NewWorkItem() Using f As New frmAWorkItem(wi) f.ShowDialog(Me) End Using End If
基本上來說,將工作項類型的字符串名稱傳遞到 WorkItemTypes 集合對象並調用其 NewWorkItem 方 法。然後,代碼將新工作項實例傳遞到 frmAWorkItem 對象的構造函數,該構造函數將新工作項綁定到 WorkItemForm 控件。
總結
Team Foundation Server 提供的工作項跟蹤子系統公開了豐富的對象模型,以便與您自己的解決方案 集成在一起。從任何應用程序訪問工作項信息僅需要幾行代碼。此外,生成應用程序時,可以讓內置控件 執行繁重的工作,以提供豐富而一致的界面。在下一個專欄中,我將介紹如何將工作項支持集成到上一專 欄中開始的外接程序。
將您想詢問的問題和提出的意見發送至:[email protected] [email protected].
本文配套源碼:http://www.bianceng.net/dotnet/201212/832.htm