65.9K
CodeProject 正在变化。 阅读更多。
Home

TFS 工作项的表达式控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.55/5 (8投票s)

2010 年 10 月 1 日

CPOL

16分钟阅读

viewsIcon

68715

downloadIcon

2180

TFS 工作项的自定义控件,用于显示基于工作项字段内容计算出的表达式结果

引言

本文介绍了一个用于 Team Foundation Server 工作项表单的自定义控件。该控件是一个 `textbox`,用于显示数学表达式的计算结果,该数学表达式可以包含工作项中的其他数字字段。

背景

注意:本文并非旨在提供创建/修改 TFS 工作项或自定义控件的全面指南。有关详细信息,请参阅 “更多信息”部分中的链接。

Team Foundation Server 是一个优秀的应用程序生命周期管理平台。它提供的一项功能是能够创建和管理工作项,这些工作项封装了单个的工作单元。

TFS 开箱即用地提供了标准的“需求”、“Bug”、“任务”、“风险”等工作项类型,用于管理软件开发项目中常见的各种工件(这些项在两个项目模板中定义:MSF for Agile Software Development 和 MSF for CMMI Process Improvement)。然而,也可以定义新的工作项类型并修改现有的工作项类型(项目模板也是如此)。

工作项包含用于存储各种信息的字段;典型的字段包括标题、工作项创建者姓名、分配的优先级或估算的实现工作量。为了编辑这些字段的信息,每个工作项还定义了一个包含各种可编辑控件表单的布局(也有不可编辑的控件,例如工作项更改历史记录)。当您创建新工作项或编辑现有工作项时,Team Explorer(Visual Studio 的一个组件)会显示这些表单。

Team Explorer 提供了几种现成的控件,例如简单的单行文本框、HTML 富文本控件、日期选择器、下拉列表或浏览器。可以创建新型控件并在 TFS 工作项表单中使用它们,这也是本文将要介绍的内容。

那么,这是什么?

在本文中,您将了解到 `ExpressionControl`,一个 TFS 自定义控件,用于显示数学表达式的计算结果。它的优点在于您可以在表达式中引用工作项的其他字段,并且如果更改其中一个字段的值,表达式将重新计算,控件上的值也会自动更新。如果您将一个工作项字段与该控件关联,计算值将存储在工作项本身中,因此可用于工作项查询和报告。

作为概念验证,我提供了一个修改版的 Risk 工作项,该工作项来自 MSF for CMMI Process Improvement 项目模板。在此版本中,我用“Impact”(影响)字段替换了“Severity”(严重性)字段,这是一个可以接受任何值的数字字段。我还添加了一个“Exposure”(风险暴露度)字段,该字段由 `ExpressionControl` 自动计算为“Probability”(概率)乘以“Impact”(影响)。

控件的源代码以 Visual Studio 2010 解决方案的形式提供,该解决方案包含一个 C# 类库和一个安装程序项目。您可以将此解决方案用作创建自己的 TFS 自定义控件的框架。

安装和使用控件

如果您想立即安装和使用 `ExpressionControl`,请按照以下步骤操作

  1. 显而易见,但请确保您已安装 Team Explorer 2010。
  2. “下载”部分下载安装程序。
  3. 运行安装程序。这只会将两个 `.dll` 文件和一个 `.wicc` 文件(只是一个 XML 文件)复制到 Visual Studio 安装文件夹下的一个特殊子文件夹中。

现在,您可以将修改后的 Risk 工作项定义上传到服务器。您可以使用 Team Explorer 附带的 `witadmin` 工具来完成此操作。打开 Visual Studio 命令提示符并执行

witadmin importwitd /collection:(URL_of_your_TFS_server_and_collection) 
/p:(name_of_the_team_project) /f:Risk.wit

或者,如果您已在 Visual Studio 中安装了 TFS Power Tools,请在 Visual Studio 菜单中选择 `Tools - Process Editor - Work Item Types - Import WIT`。

现在尝试创建一个名为 Risk 的新工作项。您会发现,当您修改 Probability 或 Impact 字段时,Exposure 字段会自动更新。

Animation on how modifying Probability or Impact cause the Exposure to be recalculated

更通用地说,在工作项中使用该控件的方法是定义一个类型为 `ExpressionControl` 的新控件,并添加一个名为 `Expression` 的属性,其值为要计算的表达式。该表达式可以包含基本运算(+ - * /),操作数可以是数字,也可以是用方括号括起来的字段名,例如 `[Priority]`。例如,在修改后的 Risk 定义中,我们可以找到以下行

<Control
   FieldName = "Konamiman.TFS.Exposure"
   Type = "ExpressionControl"
   Expression = "[Probability]*[Impact]"
   Label = "Exposure:"
   LabelPosition = "Left"
   ReadOnly = "True">

将字段与控件关联不是强制性的,但如果您希望将计算值持久化到工作项中,则这是必需的。例如,我们可以创建一个显示具有优先级、影响和风险暴露度的 Risk 的工作项查询。

Sample of risk list query

对于修改后的 Risk 工作项,我们定义了一个后备字段如下:

<FIELD name="Exposure" refname="Konamiman.TFS.Exposure" type="Integer">
    <HELPTEXT>Exposure of the risk, calculated as probability by impact.</HELPTEXT>
</FIELD>

如果您知道计算结果将为非整数,则可能希望将字段定义为 `double` 而不是 `integer`。例如,您可以将表达式定义为 `([Probability]*[Impact])/100`。

要将新字段和控件插入现有工作项,您需要从服务器下载工作项定义文件。使用 `witadmin` 工具,您将执行:

witadmin exportwitd /collection:(URL_of_your_TFS_server_and_collection) 
/p:(name_of_the_team_project) /n:(work_item_type_name) /f:(destination_file_name>

或者使用 TFS Power Tools,在 Visual Studio 菜单中选择 `Tools - Process Editor - Work Item Types - Export WIT`。

项目

TFS 自定义控件只是一个实现了 IWorkItemControl 接口的 Windows Forms 用户控件,并附带一个 XML 格式的描述符文件(`.wicc`)(有关详细信息,请参阅 “更多信息”部分中的链接)。因此,创建自定义控件所需的步骤如下:

  1. 创建一个新的 Visual Studio 解决方案,类型为“类库”。
  2. 添加对 `System.Windows.Forms` 和 `System.Drawing` 系统程序集的引用。
  3. 添加对以下 Team Explorer 程序集的引用:
    %ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\
    Microsoft.TeamFoundation.WorkItemTracking.Controls.dll
    %ProgramFiles%\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\
    v2.0\Microsoft.TeamFoundation.WorkItemTracking.Client.dll

    注意:在您的系统中,根目录可能是 `%ProgramFiles(x86)%`。)

  4. 添加一个新项,类型为“用户控件”,并使其实现 `IWorkItemControl`。
  5. 在用户控件设计器表面添加适当的控件。
  6. 添加 `IWorkItemControl` 的代码(稍后将详细介绍)。
  7. 添加一个新 XML 文件,将其命名为 `YourControlName.wicc`(文件名将是 TFS 用来引用您的控件的名称),并将其编译操作设置为“Content”。文件的内容必须如下:

    <?xml version="1.0"?>
    <CustomControl xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance 
    	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Assembly>YourClassLibraryFileName.dll</Assembly>
      <FullClassName>YourControlFullClassName</FullClassName>
    </CustomControl>

编译代码后,您需要将生成的 `.dll` 文件与 `.wicc` 文件一起复制到以下位置:

%ProgramData%\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0

就是这样,Team Explorer 将识别您的控件并显示它,只要您已在工作项中声明了它。

安装程序

尽管部署控件就像将两个文件复制到 TFS 自定义控件文件夹一样简单,但拥有一个安装程序项目来完成此任务也是不错的选择。 Nick Hoggard 解释了如何创建这样的项目,但是由于我比他多做了几件事,我将在此描述我为创建安装程序所遵循的完整过程。

  1. 在解决方案中创建一个新项目,类型为“Setup project”。
  2. 打开“File System Editor”选项卡,并创建一个类型为“Custom Folder”的新文件夹,命名为 `CommonAppDataFolder`。将其 `DefaultLocation` 属性设置为 `[CommonAppDataFolder]`。
  3. 在 `CommonAppData` 文件夹下,按顺序创建一个文件夹层次结构 `Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0`。请参阅以下截图,该截图摘自 Hoggart 的页面本身。

    Folder hierarchy created in the setup project

  4. 右键单击 10.0 文件夹,然后选择“Add - Project Output”。从您的类库项目中选择“Primary Output”和“Content Files”(可选,您也可以选择“Debug Symbols”,这在您想调试代码时很有用)。
  5. 在 Solution Explorer 中设置项目的“Detected Dependencies”文件夹下,选择所有检测到的 `Microsoft.TeamFoundation.*` 依赖项,右键单击并选择“Exclude”,以避免安装程序将它们与您的库一起复制。
  6. 在“User Interface Editor”窗格中,删除第二屏和第三屏。这些是安装位置选择屏幕(不是必需的,因为文件始终安装在固定位置)和安装确认屏幕(如果删除了安装位置选择屏幕,则无用)。当然,除非您想让用户选择是为所有用户安装还是仅为当前用户安装(我总是将 `InstallAllUser` 项目属性设置为 `True`)。

    现在,我们将添加一个启动条件,以避免在未安装 Team Explorer 的情况下运行安装程序。这严格来说不是必需的,但会让我们的安装程序更整洁。

  7. 打开“Launch Conditions Editor”选项卡。
  8. 右键单击“Search Target Machine”,然后选择“Add Registry Search”。
  9. 选择刚添加的注册表搜索,打开属性窗口,并按如下方式设置属性:Name = Team Explorer 2010, Property = TEAMEXPLORERINSTALLED, Root = vsdrrHKCU, RegKey = `Software\Microsoft\VisualStudio\10.0_Config\InstalledProducts\Team Explorer`。请参阅以下截图。

    How the registry search condition is to be configured

  10. 右键单击“Launch Conditions”,然后选择“Add Launch Condition”。
  11. 选择刚添加的启动条件,打开属性窗口,并按如下方式设置属性:Name = Team Explorer 2010, Condition = TEAMEXPLORERINSTALLED, Message = Error: Team Explorer 2010 is not installed. 请参阅以下截图。

    How the launch condition is to be configured

最后一步是适当地配置设置项目属性(作者、描述、URL 等)。您可以通过将“Konamiman”替换为您自己的姓名或公司名称来为自己使用提供的设置项目(以及类库项目)。

代码

现在我将解释控件代码本身的关键点。如果您想仔细查看,可以 下载 Visual Studio 解决方案

表达式求值器

首先,我必须说我没有自己编写表达式求值器。相反,我使用了非常出色的 Sebastien Ros 的 State of the Art Expression Evaluation,它提供了我所需的所有功能,甚至更多。我建议您查看 Sebastien 的文章,但为了理解在 `ExpressionControl` 中如何使用求值器,您需要了解以下基本要点:

  • 求值器的核心是 `Expression` 类。在实例化该类时,您传递一个要计算的表达式的 `string`,例如:
    var expression = new Expression("([Priority] * [Impact]) / 100");
  • 您使用 `Expression` 对象的 `Evaluate` 方法来触发表达式求值。该方法返回一个包含求值结果的对象(`ExpressionControl` 只处理数字,但 `Expression` 类也可以处理字符串、布尔值和 `DateTime`)。
  • `Expression` 类可以处理参数,即在求值时动态解析的(用方括号括起来的)字母数字标识符。在求值过程中找到参数时,会触发一个名为 `EvaluateParameter` 的事件。此事件提供参数名称,并期望将参数值设置在关联的 `EventArgs` 的某个属性上。例如:
    Expression e = new Expression("2 * [Pi]");
    
    e.EvaluateParameter += delegate(string name, ParameterArgs args)
    {
        if (name == "Pi") {
            args.Result = 3.14;
        }
    };
    
    var result = e.Evaluate();

表达式求值器的源代码嵌入在 `ExpressionControl` 项目本身中,位于 `ExpressionEvaluator` 文件夹下。求值器代码依赖于一个库 `Evaluant.Antlr.dll`,该库已被项目引用(当然,在安装时必须与控件库一起复制)。

控件代码

`ExpressionControl` 的可视化界面只是一个用于显示表达式求值结果的单行 `TextBox`。根据在包含工作项表单中如何配置控件的 `ReadOnly` 属性,它可以是只读的,也可以不是(当然,建议将其配置为只读)。Team Explorer 在您创建新工作项或选择现有工作项进行编辑时会创建控件的实例,前提是该控件已在工作项表单定义中声明。

我现在将枚举 `ExpressionControl` 类的主要成员,解释每个成员的关键点,以便您能对控件的工作方式有一个大致的了解。

  • IWorkItemControl.Properties:这是一个 `StringDictionary`,其中包含控件的所有属性,这些属性在工作项表单定义(XML `Control` 元素的所有 XML 属性都包含在此处)中声明。当设置此属性时,我们会检索 `Expression` 属性并用它来实例化一个 `Expression` 对象,该对象的 `EvaluateParameter` 事件已分配给一个处理方法(`expression_EvaluateParameter`)。
  • IWorkItemControl.WorkItemDatasource:这是包含该控件的表单正在编辑的工作项(尽管属性声明为 `Object`,但值始终为 `WorkItem` 类型)。当设置此属性时,我们会为工作项的 `FieldChanged` 事件(`OnFieldChanged` 方法)分配一个处理程序,以便在必要时能够适当地重新评估表达式并更新控件值。
  • IWorkItemControl.FlushToDatasource:当需要将控件值备份到工作项的关联字段时(以便值与工作项数据一起持久化到 TFS 数据库中),会调用此方法。我们只需检查控件是否实际与工作项字段关联(如果未关联,则无处持久化),并且该字段是否为数字类型(字符串字段也可以接受,在这种情况下,值在设置之前简单地转换为 `ToString`),然后设置字段的新值。
  • GenerateNewTextboxValue:在工作项表单加载时(通过 `IWorkItemControl.InvalidateDatasource` 方法)以及表达式中引用的字段之一更改时(通过 `OnFieldChanged` 方法)调用此方法。它只是调用 `Expression` 对象的 `Evaluate` 方法,并将结果作为 `string` 返回以设置到 `TextBox`(结果也缓存在 `calculationValue` 类字段中,以便 `FlushToDatasource` 方法可以使用它)。该方法处理所有可能的错误情况,在必要时返回适当的错误消息而不是计算结果。
  • expression_EvaluateParameter:每当表达式求值器遇到表达式中的参数时,都会调用此方法。我们假设参数是字段名,并查询工作项以获取字段值。如果工作项未声明具有该名称的字段,或者字段不是数字类型,我们将抛出异常,该异常将被 `GenerateNewTextboxValue` 方法捕获并转换为错误消息。

以上就是控件的大部分内容。还有一些辅助成员,但控件的核心在于上面列出的成员。

调试

当我完成控件后,我发现它工作得很好,除了一个细节:当保存包含该控件的工作项时,Visual Studio 会显示一个“对象引用未设置为对象实例”的消息框,但否则会正确保存工作项。结果发现,`FieldChanged` 事件在工作项即将保存时触发,但 `WorkItemEventArgs` 参数的 `Field` 属性为 `null`。我不知道这是“bug 还是 feature”,但据我所见,忽略 `Field` 属性为 `null` 的事件似乎是安全的。

如果我没有调试代码,我永远找不到这个 bug。但是,您不能像调试普通 Visual Studio 项目那样直接调试 TFS 自定义控件(您无法“运行”它)。那么,我们能做什么呢? MSDN 有答案(向下滚动直到看到已接受的答案)。请注意:您需要跳过步骤 10 和 11(您无法“启动”类库项目)。为完整起见,我在此粘贴了该过程(原始内容引用的是 VS 2008,我已经将引用更改为 VS 2010)。

  1. 打开 VS 2010 并加载您的项目。
  2. 删除项目中的所有断点。
  3. 右键单击项目并转到项目属性。
  4. 对于配置,选择“Active (Debug)”。
  5. 在“Enable debuggers”部分,勾选“Enable unmanaged code debugging”并启用“Visual Studio hosting process”。
  6. 保存。
  7. 右键单击您的项目并单击“Clean”。
  8. 再次右键单击您的项目并单击“Rebuild”。
  9. 在 Windows Explorer 中,转到 `debug` 文件夹并将输出(`.dll`、`.exe` 等)复制到 `C:\Documents and Settings\All Users\Application Data\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\10.0`(这是 TF 2010 WI 查找 `.dll` 和 `.wicc` 文件的位置)。
  10. (跳过此项!)在工具栏上,将下拉列表设置为“debug”,然后单击箭头。
  11. (跳过此项!)您将看到一个带有控件的小屏幕。应该有一个“load”按钮。单击它,并确保从步骤 9 中指定的路径加载 `.dll` / `.exe`。
  12. 保持打开状态,不要关闭任何东西。打开另一个 VS 2010 实例。
  13. 在 Team Explorer 中,打开包含您的自定义控件的工作项类型(Add Work Item-> . . . . . )。
  14. 返回到 VS 2010 的第一个实例(步骤 1)并转到 Debug->Attach to Process。
  15. 在“Attach to Process”窗口中,将“Transport”保留为“Default”,将“Attach To”字段设置为选中“managed code”和“native code”。
  16. 在“Available processes”窗口中,您将看到进程 `devenv.exe` 及其 ID 和您刚刚创建的工作项的标题。选择该行并单击“Attach”。
  17. 您将看到一些后台处理,因为它正在加载符号。完成后,相应地设置您的断点。
  18. 返回到您的第二个 VS2010 实例(步骤 12),并执行您需要对在步骤 13 中创建的工作项执行的操作,以确保命中断点。
  19. 一旦您认为断点已命中,请返回到第一个 VS2010 实例(步骤 1 和步骤 17),您将看到您设置的断点已命中。现在您只需调试(叹气!)。

更多信息

如果您想了解更多关于 TFS 工作项自定义和 TFS 自定义控件的信息,请参阅以下资源:

最后说明

这是我的第一个 TFS 自定义控件,因此代码可能包含错误。如果出现这种情况,或者您有改进建议,请给我留言。

历史

  • 2010 年 10 月 1 日:第一个版本
  • 2010 年 10 月 6 日:更新了屏幕截图,现在所有 Visual Studio 标签都以英语显示。
© . All rights reserved.