在 Visual Studio 中创建自定义 UI 属性页/属性表 - 第一部分





5.00/5 (8投票s)
使用 DynamicEnumProperty、EnumProperty、IntProperty、ProjectSchemaDefinitions、Rule、StringListProperty 和 StringProperty 元素来创建 Visual Studio (MSBuild) 中的属性表
引言
任何使用过 Visual Studio 的人都知道属性设置对话框长什么样子(就是那个带粉色边框的)
它允许开发人员设置构建过程中使用的所有工具的所有可用选项。并非所有人都知道,这个对话框并非 Visual Studio 中一个整体的内置对话框,而是一个模块化的结构,它在加载项目时根据配置文件构建而成。这些属性页的配置和功能取决于项目类型,并来自位于 $(VCTargetsPath)\...
的 XML 文件。
在本文中,我想介绍在 Visual Studio 中为自定义项目创建属性页所需的步骤。在我的 第二篇文章 中,我将解释如何使用这些属性为自定义构建生成命令行选项。
背景
MSBuild
Microsoft Build Engine,也称为 MSBuild,是一个用于原生和托管代码的构建平台,是 Windows 软件开发工具包的一部分。Visual Studio 依赖 MSBuild 并使用它进行项目构建,但 MSBuild 不依赖 Visual Studio,可以独立使用。有关 MSBuild 的深入信息,请参阅 MSBuild 参考。
原理
最近,我不得不将几个开源库集成到 Visual Studio 项目中,发现这相当具有挑战性。每个库都需要多个构建步骤,并且有大量的开关和命令行属性需要处理。我创建了 MSBuild 项目来执行这些步骤,但仍然面临着设置所有构建参数的问题。因此,我不得不为这些设置创建 UI。
我很快发现关于这个主题的文档并不容易获得。我花了一些时间试图弄清楚什么东西放在哪里以及为什么。本文是我发现的简要总结。它绝不是对该主题的详尽覆盖,而是一个正在进行的工作…
项目
属性页由项目加载。用 Visual Studio 创建的项目默认包含所有必要的组件并加载它们。但如果你从头开始,你需要自己创建一个项目。
创建项目
MSBuild 项目只是一个简单的 XML 文件。你可以在 MSDN 上了解更多。当项目在 Visual Studio 中从提供的模板之一创建时,VS 会添加所有必需的包含项、默认属性和目标。它还会加载所有适合项目类型的属性表。
如果项目是手动创建的,默认情况下不会加载任何内容,必须显式完成。实际上,要正确完成此操作需要很多步骤,但创建项目文件超出了本文的范围。有关此主题的更多信息,我推荐优秀的 MSBuild 书籍 或 MSDN 库。
项目属性 UI
在项目中创建所有必要的 Target、Item 和 Property,将项目加载到 Visual Studio 中,并打开“属性”对话框后,我们会得到类似这样的结果
项目中不会加载任何 UI 属性表。我们必须创建它们并将它们加载到项目中。
属性页
每个属性页显示在“属性页”对话框的“引用”框下。将属性页添加到项目中需要两个步骤:创建页面的架构并将其导入到项目中。Visual Studio 加载架构以创建其中描述的所有 UI 控件,并在构建过程中忽略它。
将属性页架构导入到项目中
Visual Studio 在一个特殊的 Item 中跟踪所有属性页,该 Item 称为 PropertyPageSchema
。要添加新页面,我们可以这样做:
<ItemGroup>
<PropertyPageSchema Include="boost.xml" >
</ItemGroup>
如果我们正在扩展默认项目类型(在 Visual Studio 的“新建项目”向导中创建),并且已经加载了默认属性表,那么这将足以显示该页面。但如果这是项目中唯一的页面,则该页面不会显示。配置子系统至少需要一个涵盖整个项目的属性页才能显示任何页面。我们必须指定此页面适用于整个项目。这可以通过将 Context
元数据指定为 'Project
' 来完成。
<ItemGroup>
<PropertyPageSchema Include="boost.xml" >
<Context>Project</Context>
</PropertyPageSchema>
</ItemGroup>
通常,属性页可以适用于整个项目、任何文件或特定类型的文件等。Context
属性的有效值为:Project、File、ProjectSubscriptionService、BrowseObject,或这些的逗号分隔组合。
创建属性页架构
属性页架构是一个常规的 XML 文件。它具有以下结构
<?xml version="1.0" encoding="utf-8"?>
<Rule ... xmlns="http://schemas.microsoft.com/build/2009/properties">
...
</Rule>
架构可以包含单个 Rule,如上所示,或者它可以在单个文件中包含多个 Rule 以及其他类型。如果文件包含多个定义,则必须用 ProjectSchemaDefinitions
元素将其包装起来。请注意,所有命名空间声明都必须移到 ProjectSchemaDefinitions
元素中。
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Rule ... Name="Link1">
...
</Rule>
<Rule ... Name="Link2">
...
</Rule>
<ContentType>...<ContentType>
<ItemType Name="..." DisplayName="..." />
<FileExtension Name=".res" ContentType="PRIResource"/>
</ProjectSchemaDefinitions>
XML 中描述的每个项最终都将被 XAML 反序列化器处理并实例化为 Microsoft.Build.Framework.XamlTypes 命名空间下的 .NET 类实例。可以将其视为为 WPF 引擎创建 XAML 模板。
关于此主题的信息相当稀少。有关 Rule 的基本参考信息可在 MSDN 上找到。Pavan Adharapurapu 的博文“Platform extensibility - Part 2”提供了更深入的属性解释。在这里,我将综合这两个来源的材料以及我自己的发现。
规则
一个 Rule 元素 是所有魔法发生的地方。它完整地描述了属性页,包括数据存储位置、类别和子类别、控件和编辑器等。典型的 Rule 如下所示:
<Rule Name="..." DisplayName="..." Description="..." PageTemplate="..." ...
xmlns="http://schemas.microsoft.com/build/2009/properties">
<Rule.Categories>
<Category Name="..." DisplayName="..." />
</Rule.Categories>
<Rule.DataSource>
<DataSource Persistence="ProjectFile" ... />
</Rule.DataSource>
<StringProperty ... />
<StringListProperty ... />
<IntProperty ... />
<BoolProperty ... />
<EnumProperty />
<DynamicEnumProperty .. />
</Rule>
有关 Rule 元素 的完整属性列表可在 MSDN 上找到。我将只描述最相关的属性和特性。
Rule 属性
命名空间 (xmlns)
这是一个标准的 XML 元素。在上面的示例中,您可以看到列出了三个命名空间。这些对应于 XAML 反序列化类、XAML 架构和系统命名空间以及配置属性的命名空间。Rule 至少需要包含此命名空间:xmlns="http://schemas.microsoft.com/build/2009/properties"。
名称
Name 属性是 Rule 的 ID。它需要与项目所有属性页 XML 文件中的其他 Rule 唯一。
DisplayName
这是在属性页 UI 中显示的 Rule 节点的名称。此值已本地化。我们创建 DisplayName 作为 Rule 的子元素而不是属性(如 Name 或 SwitchPrefix),因为内部本地化工具需要。从 XAML 的角度来看,两者是等效的。因此,您可以将其设为属性以减少混乱,或按原样保留。
描述
向用户描述此 Rule。
PageTemplate
此属性的值用于 UI 从 UI 模板集合中进行选择。
generic |
generic 模板在一个页面上显示不同的属性和类别。 | ![]() |
tool |
tool 模板将类别渲染为 Rule 主节点下的子节点。属性以标准的网格格式显示在每个类别中。 Tool 模板允许添加 All Options 和 Command Line 类别。 |
![]() |
debugger |
debugger 页面模板的 UI 使用下拉框在不同调试器的属性之间切换。 | ![]() |
XML 文件设计为 UI 独立。因此,不同的 UI 可以将此属性用于不同的目的。
Order
这是对潜在 UI 客户端的建议,说明此 Rule 相对于系统中所有其他 Rule 的相对位置。
DataSource
DataSource 属性指定了存储此 Rule 中属性数据的默认位置。对于任何属性,都可以通过在属性中指定数据源来覆盖此位置。如下例所示:
...
<Rule.DataSource>
<DataSource Persistence="ProjectFile" ItemType="" Label="" HasConfigurationCondition="true" ... />
</Rule.DataSource>
<StringListProperty Name="StringName" ... />
<BoolProperty Name="BoolName" ... />
<BoolProperty.DataSource>
<DataSource Persistence="ProjectFile" PersistedName="OtherBoolName" ... />
</BoolProperty.DataSource>
</BoolProperty>
<EnumProperty Name="EnumName" ... />
</Rule>
持久化 |
此属性告诉项目系统在哪里存储 Rule 的值。它可以是: ProjectFile - 属性应写入项目文件;或 UserFile - 属性写入 $(ProjectName).user 文件。 ResolvedReference - 执行 MSBuildTarget 属性中指定的 ResolveProjectReferencesDesignTime 目标的结果。 |
PersistedName |
此属性指定一个用于在项目文件中读取/写入属性值的名称。通常,属性使用属性的名称保存,例如:StringName、BoolName、EnumName 等(参见上面的代码示例)。但当定义了 PersistedName 时,则会使用该名称:StringName, OtherBoolName, EnumName 等。在默认 DataSource 上设置此属性没有多大意义,因为它会将所有属性保存到同一个覆盖的名称中,从而丢失所有数据。 |
ItemType |
当此属性为空时,所有属性都将存储在项目文件中的某个 PropertyGroup 中,作为 常规 MSBuild Properties。 指定 ItemType(例如 ClCompile )会强制将属性存储为该项类型的 ItemDefinition 元数据或项元数据(后者仅当属性页是从解决方案资源管理器中的文件节点生成的)。 |
HasConfigurationCondition |
告诉项目系统将配置条件附加到该值,使其仅对当前项目配置生效或不生效。<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> ... </ItemDefinitionGroup> |
SourceType |
指定数据源的类型。有效类型为: Item - 当数据源是 ItemDefinitionGroup 中定义的实体时。TargetResults - 当数据源是 MSBuildTarget 属性中指定的 Target 时。 |
MSBuildTarget |
必须在读取此数据源的只读属性或项之前执行的 MSBuild Target 的分号分隔列表。Target 返回的数据将成为 DataSource。 |
SourceOfDefaultValue |
指向 DefaultValueSourceLocation 的值,该值指示此数据源的默认值位置。 |
分类
Categories
是一个可选属性,包含一个包含的类别列表。条目的顺序决定了属性对话框“引用”窗口中类别的顺序。
Rule 元素的正文
嵌套在 Rule 元素中是描述属性表的配置属性。
以下元素可包含在 Rule 中:
StringProperty
StringListProperty
IntProperty
EnumProperty
DynamicEnumProperty
BoolProperty
配置属性
所有这些元素都派生自同一个基本类,因此共享一组共同的属性。
通用属性
大多数这些属性都是不言而喻的。通用属性的完整列表可在 MSDN 上找到。我将列出一些不太明显的属性:
类别
指定属性的类别。在 generic 模板中,此属性在粗体显示的类别下分组。Tool 模板会将此类别渲染为对话框“引用”窗口中 Rule 节点的子类别。即使此类别未在 Rule 的 Categories 元素中列出,它也会在 UI 中显示。
DataSource
如前所述,每个属性都可以覆盖父 Rule 中定义的 DataSource。一旦指定,此属性将将其值存储到该 DataSource 中。
IncludeInCommandLine
如果 PageTemplate 为 tool
且 Categories
包含 CommandLine
子类型,如下所示:
<Rule
...
<Rule.Categories>
<Category Name="Command Line" Subtype="CommandLine" DisplayName="Command Line" />
</Rule.Categories>
...
IncludeInCommandLine 决定此属性的开关是否包含在生成的显示中。
SwitchPrefix
覆盖 Rule
元素中定义的 SwitchPrefix
。
Switch
此值包含一个字符串,用于命令行开关。例如,如果 Switch="I",命令行属性表将显示为 /I。格式为 $(SwitchPrefix)$(Switch)。此属性由 CommandLine 属性表用于生成当前开关集。
Visible
此属性决定此属性是否显示在属性页和命令行属性表中。
ValueEditors
允许您为该属性关联特定的值编辑器。
分隔符
指定用于分隔开关与其值的令牌。格式如下:$(SwitchPrefix)$(Switch)$(Separator)$(Value)
有关更多信息,请参阅 Microsoft.Build.Framework.XamlTypes.BasePropertyStringProperty 元素
此属性允许输入和编辑文本数据。字符串的值存储在与属性同名的变量中,除非在 PersistedName
中覆盖。在 CommandLine
属性表中渲染时,使用的格式为:$(SwitchPrefix)$(Switch)$(Separator)$(StringProperty)。
Subtype
限定此字符串属性,以赋予其更具体的分类。常见的子类型包括:
Folder - 属性的值代表文件夹的路径,不包含文件名。
File - 属性的值是文件的路径。
<StringProperty Name="OutDir" SwitchPrefix="-" Switch="outdir" Separator="=" ... />
在属性表中输入值 "C:\Temp\" 将存储为 <OutDir>
C:\Temp\</OutDir>
,并在命令行窗口中渲染为:
-outdir="C:\Temp\"。
有关更多信息,请参阅 Microsoft.Build.Framework.XamlTypes.StringProperty
StringListProperty 元素
此属性与 StringProperty
相同,除了它包含字符串值列表。当指定 SubType
时,它与 StringProperty
共享相同的行为,并且在格式上与 StringProperty
相同,只是在字符串值之间添加了一个分隔符。StringListProperty 将列表连接成一个字符串,其中各个字符串值由分隔符分隔。默认分隔符是分号 ";"。分隔符存储在以下属性中:
CommandLineValueSeparator
指定用于分隔此字符串列表属性各个值的分隔符。
示例<StringListProperty Name="DisableSpecificWarnings" CommandLineValueSeparator="," Switch="wd" ... />
在属性表中输入值 1234、4567 和 5678 将存储为 <DisableSpecificWarnings>
1234,4567,5678</DisableSpecificWarnings>
。
有关更多信息,请参阅 Microsoft.Build.Framework.XamlTypes.StringListProperty
IntProperty 元素
此属性允许输入和编辑数值数据。它定义了两个属性来存储此属性允许的 Max
和 Min
值:
最大值
此属性允许的最大值。
最小值
此属性允许的最小值。
有关更多信息,请参阅 Microsoft.Build.Framework.XamlTypes.IntProperty
BoolProperty 元素
此属性允许输入和编辑布尔数据。它可以是 'true'、'false' 或空。它定义了一个附加属性 ReverseSwitch
来表示 'false' 状态。
ReverseSwitch
一个标志,强制对布尔开关的值进行逻辑取反。
示例<BoolProperty SwitchPrefix="/" ReverseSwitch="sdl-" Name="SDLCheck" Switch="sdl" ... />
在属性表中选择 Yes 将存储为 <SDLCheck>
true</SDLCheck>
,并在命令行中渲染为 /sdl
在属性表中选择 No 将存储为 <SDLCheck>
false</SDLCheck>
,并在命令行中渲染为 /sdl-
有关更多信息,请参阅 Microsoft.Build.Framework.XamlTypes.BoolProperty
EnumProperty 元素
EnumProperty 允许从一组可能值中选择一个选项。当选择其中一个时,它存储 EnumValue
元素的名称。可能选项由 <EnumValue>
元素定义。EnumProperty
还定义了 AdmissibleValue
属性来存储可接受的值。
AdmissibleValues
指定此属性的可能值的列表。
示例
<EnumProperty Name="WarningLevel" SwitchPrefix="/" ... >
<EnumValue Name="TurnOffAllWarnings" Switch="W0" DisplayName="Turn Off All Warnings" />
<EnumValue Name="Level1" Switch="W1" DisplayName="Level 1" />
<EnumValue Name="Level2" Switch="W2" DisplayName="Level 2" />
<EnumValue Name="Level3" Switch="W3" DisplayName="Level 3" />
<EnumValue Name="Level4" Switch="W4" DisplayName="Level 4" />
<EnumValue Name="EnableAllWarnings" Switch="Wall" DisplayName="EnableAllWarnings" />
</EnumProperty>
在属性表中选择第一个选项将存储 <WarningLevel>
TurnOffAllWarnings</
WarningLevel>
,并在命令行窗口中渲染为 /w0。
选择第二个选项存储<
WarningLevel>
Level1</
WarningLevel>
,并在命令行中渲染为 /w1。
选择最后一个选项存储<
WarningLevel>
EnableAllWarnings</
WarningLevel>
,并在命令行中渲染为 /w1。
有关更多信息,请参阅 Microsoft.Build.Framework.XamlTypes.EnumProperty
DynamicEnumProperty 元素
DynamicEnumProperty
与 EnumProperty
非常相似,唯一的区别是 Enum 值来自动态枚举提供程序。此属性定义了 EnumProvider
和 ProviderSettings
两个属性来指定提供程序数据。
EnumProvider
指定生成此属性可能值列表的提供程序。
ProviderSettings
指定一个提供程序特定的选项集以传递给提供程序。
示例
<DynamicEnumProperty Name="PlatformToolset" DisplayName="Platform Toolset" EnumProvider="Toolsets" />
有关更多信息,请参阅 Microsoft.Build.Framework.XamlTypes.EnumProperty
有关这些元素的更深入的介绍,请参阅 Visual Studio 中创建自定义 UI 属性页/属性表 - 第二部分
历史
2015/03/03 - 发布。
2015/03/09 - 修正了一些不正确的陈述并添加了第二部分的链接。