Creating custom UI property pages/sheets in Visual Studio Part 1





5.00/5 (8 votes)
Using DynamicEnumProperty, EnumProperty, IntProperty, ProjectSchemaDefinitions, Rule, StringListProperty, and StringProperty elements for creation of property sheets in Visual Studio (MSBuild)
Introduction
Anyone who worked with Visual Studio knows what property settings dialog looks like (one with the pink frame):
It allows developer to set all available options for all the tools used in build process. Not everybody knows that this dialog is not a monolithic, built-in dialog of Visual Studio but rather modular construct, built from configuration files upon loading of the project. The configuration and features of these property pages are project type dependent and come from XML files located at $(VCTargetsPath)\...
In this article I’d like to go over steps required to create property pages for use in Visual Studio with custom projects. In my second article I'll explain how these properties could be used to generate commant line options for custom builds.
Background
MSBuild
Microsoft Build Engine, also known as MSBuild, is a build platform for native and managed code and is part of Windows Software Development Kit. Visual Studio depends on MSBuild and uses it for project builds, but MSBuild does not depend on Visual Studio and could be used stand alone. In depth information about MSBuild is available at MSBuild reference.
Rationale
Lately I had to integrate several open source libraries into Visual Studio project and found it quite challenging. Each of these required multiple steps to build and had tons of switches and command line attributes to deal with. I’ve creates MSBuild projects to perform these steps but was still left with the issue of setting up all the build parameters. So I had to create UI for these settings.
As I immediately discovered the documentation on this topic is not easily available. I had to spend some time trying to figure out what goes where and why. This article is a short summary of my findings. It is in no way exhaustive coverage of the subject but rather work in progress…
Project
Property pages are loaded by the projects. Projects created with Visual Studio have all the necessary components included and loaded by default. But if you starting from scratch, you need to create a project yourself.
Creating project
MSBuild project is just a simple XML file. You can learn more about it at MSDN. When project is created in Visual Studio from one of provided templates VS adds all necessary includes, default properties and targets. It also loads all the appropriate property sheets for the project type.
If project created manually, none of that is loaded by default, it have to be done explicitly. In reality it takes quite a few steps to do it correctly but creating a project file is outside of the scope of this article. For more information on the subject I can recommend excellent MSBuild books or MSDN library.
Project Properties UI
After creating all the necessary targets, items and properties in the project, loading it into Visual Studio, and opening Properties dialog we would have something like this:
There would be no UI property sheets loaded for the project. We would have to create and load them into the project.
Property Page
Each property page shows up at the Property Pages dialog under References box. Adding property page to the project requires two steps: creating page’s schema and importing it into the project. Schema is loaded by Visual Studio to create all the UI controls described in it and is ignored during the build process.
Importing Property Page Schema into the project
Visual Studio keeps track of all property pages in special Item called PropertyPageSchema
. To add new page we could do something like this:
<ItemGroup>
<PropertyPageSchema Include="boost.xml" >
</ItemGroup>
If we are extending default project type (created in Visual Studio 'New Project' wizard) and there are default property sheets already loaded, this would be sufficient to display the page. But if this is the only page in the project, the page will not be displayed. Configuration subsystem requires at least one property page covering entire project to show any pages at all. We have to specify that this page applies to entire project. This is done by specifying Context
metadata to be 'Project
'.
<ItemGroup>
<PropertyPageSchema Include="boost.xml" >
<Context>Project</Context>
</PropertyPageSchema>
</ItemGroup>
In general property page could apply to whole project, any file or specific type of file, and etc. Valid values for Context
property are: Project, File, ProjectSubscriptionService, BrowseObject, or comma separated combination of these.
Creating Property Page Schema
Property Page Schema is a regular XML file. It has following structure:
<?xml version="1.0" encoding="utf-8"?>
<Rule ... xmlns="http://schemas.microsoft.com/build/2009/properties">
...
</Rule>
The schema could contain single rule as above or it could have multiple rules alone with other types defined in a single file. If file contains more than one definition it has to be wrapped by ProjectSchemaDefinitions
element. Note that all namespace declarations should be moved into ProjectSchemaDefinitions
element.
<?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>
Every item described by your XML ultimately will be processed and instantiated as an instance of .NET class from Microsoft.Build.Framework.XamlTypes namespace by XAML deserializers. Think of it as creating XAML templates for WPF engine.
Information on this topic is rather scarce. Basic reference information for the Rule is available at MSDN. A bit more in-depth explanation of properties is available at blog post by Pavan Adharapurapu called “Platform extensibility - Part 2”. Here I will present compilation of material from both sources as well as my own discoveries
Rule
A Rule element is where all the magic takes place. It describes property page in its entirety including location of stored data, categories and subcategories, controls and editors, and etc. Typical Rule would look like this:
<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>
Complete list of Rule element properties is available at MSDN. I will only describe most relevant properties and attributes.
Rule Attributes
namespaces (xmlns)
This is a standard XML element. In examples above you can see three namespaces listed. These correspond to the namespaces for the XAML deserialization classes, XAML schema and system namespace, and configuration properties. Rule is required to include at least this namespace: xmlns="http://schemas.microsoft.com/build/2009/properties".
Name
The Name attribute is an id for the Rule. It needs to be unique among all the property page xml files for a project.
DisplayName
This is the name that is shown on the property page UI for the Rule node. This value is localized. We created DisplayName as a child element of Rule rather than as an attribute (like Name or SwitchPrefix) because of internal localization tool requirements. From XAML's perspective, both are equivalent. So, you can just make it an attribute to reduce clutter or leave it as it is.
Description
Describes this Rule to the user.
PageTemplate
The value of this attribute is used by the UI to choose from a collection of UI templates.
The xml file is designed to be UI independent. So, a different UI could use this attribute for different purposes.
Order
This is a suggestion to a prospective UI client on the relative location of this Rule compared to all other Rules in the system
DataSource
The DataSource property specifies the default location to store data for the properties in this Rule. This location could be overridden for any property by specifying data source within the property. Like in this example:
...
<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>
Persistence |
This property tells the project system where to store values for the Rule. It could be either: ProjectFile - properties should be written to the project file or; UserFile - properties go to $(ProjectName).user file. ResolvedReference - result of executing ResolveProjectReferencesDesignTime target specified in MSBuildTarget attribute. |
PersistedName |
This property specifies a name used to read/write the value of this property in project file. Normally properties are saved using name of the property such as: StringName, BoolName, EnumName, and etc. (See code sample above.) But when PersistedName is defined it would be that name instead: StringName, OtherBoolName, EnumName, and etc.Setting this attribute on the default DataSource does not make much sense because it will save all properties with the same overridden name losing all the data. |
ItemType |
When this attribute is empty all the properties are stored as regular MSBuild Properties within some PropertyGroup in the project file. Specifying ItemType (such as ClCompile for example) forces properties to be stored as ItemDefinition metadata or item metadata (the latter only if the property pages were spawned from a file node in solution explorer) of this item type. |
HasConfigurationCondition |
Tells the project system to affix a configuration condition to the value so that it takes effect only for the current project configuration or not.
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> ... </ItemDefinitionGroup> |
SourceType |
Specifies type of the data source. Valid types are: Item - When data source is an entity defined within ItemDefinitionGroup. TargetResults - When source of data is a target specified in MSBuildTarget attribute. |
MSBuildTarget |
The semicolon-delimited list of MSBuild targets that must be executed before reading the read-only properties or items of this data source. Data returned by the targets becomes the DataSource. |
SourceOfDefaultValue |
The value of DefaultValueSourceLocation which indicates the location of the default value for this data source. |
Categories
Categories
is an optional property containing a list of included categories. Sequence of entries establishes order of categories in the References window of the Properties Dialog.
Body of Rule element
Nested inside the Rule element are the configuration properties which describe the property sheet.
Following elements are available to be included in the Rule:
StringProperty
StringListProperty
IntProperty
EnumProperty
DynamicEnumProperty
BoolProperty
Configuration Properties
All of these elements are derived from same basic class and as such share common set of attributes.
Common Attributes
Most of these properties are self-explanatory. Complete list of common properties is available at MSDN. I will list several properties which are not so obvious:
Category
Specifies the category for the property. In generic template this property is grouped under this category shown in bold. Tool template renders this category as sub-category of the Rule node in References window of the dialog. This category will be displayed in the UI even if it is not listed in Categories element of the Rule.
DataSource
As been mentioned earlier each property could override DataSource defined in parent Rule. Once specified this property will store its value into that DataSource.
IncludeInCommandLine
If PageTemplate is tool
and Categories
has CommandLine
sub-type included like this:
<Rule
...
<Rule.Categories>
<Category Name="Command Line" Subtype="CommandLine" DisplayName="Command Line" />
</Rule.Categories>
...
IncludeInCommandLine determines if switch for this Property is included into generated display.
SwitchPrefix
Overrides SwitchPrefix
defined in Rule
element.
Switch
This value contains a string used in the command line switch. For example if Switch="I" command line property sheet will display is as /I. The format is $(SwitchPrefix)$(Switch). This attribute is used by the CommandLine property sheet to generate current set of switches.
Visible
This attribute determines if this property is displayed in the property sheet and command line sheet.
ValueEditors
Allows you to associate specific value editors for this property.
Separator
Specifies the token used to separate a switch from its value. The format is as follows: $(SwitchPrefix)$(Switch)$(Separator)$(Value)
For more information please see Microsoft.Build.Framework.XamlTypes.BasePropertyStringProperty Element
This property allows entering and editing of text data. Value of the string is held in variable with the same name as the property, unless overridden in PersistedName
. When rendered in CommandLine
property sheet following format is used: $(SwitchPrefix)$(Switch)$(Separator)$(StringProperty).
Subtype
Qualifies this string property to give it a more specific classification. A well-known subtypes are:
Folder - Value of the property represents path to the folder without the file info.
File - Value of the property is path to a file
<StringProperty Name="OutDir" SwitchPrefix="-" Switch="outdir" Separator="=" ... />
Entering value "C:\Temp\" into property sheet will store it as <OutDir>
C:\Temp\</OutDir>
and renders it
as -outdir="C:\Temp\" in Command Line window.
For more information see Microsoft.Build.Framework.XamlTypes.StringProperty
StringListProperty Element
This property is the same as StringProperty
except it holds list of string values. It shares the same behavior with StringProperty
when SubType
is specified and follows the same formatting as StringProperty
with addition of separator between string values. StringListProperty concatenates list into string with individual string values divided by separator. Default separator if semicolon ";". Separator is held in following attribute:
CommandLineValueSeparator
Specifies a separator to use in delineating individual values of this string list property.
Sample:
<StringListProperty Name="DisableSpecificWarnings" CommandLineValueSeparator="," Switch="wd" ... />
Entering value 1234, 4567 and 5678 into property sheet will store it as <DisableSpecificWarnings>
1234,4567,5678</DisableSpecificWarnings>
.
For more information see Microsoft.Build.Framework.XamlTypes.StringListProperty
IntProperty Element
This property allows entering and editing numerical data. It defines two attributes to store Max
and Min
values allowed for this property:
MaxValue
Maximum allowed value for this property.
MinValue
Minimum allowed value for this property.
For more information see Microsoft.Build.Framework.XamlTypes.IntProperty
BoolProperty Element
This property allows entering and editing Boolean data. It could be 'true', 'false' or empty. It defines additional attribute ReverseSwitch to represent 'false' state:
ReverseSwitch
A flag that forces the logical negation of the value of a Boolean switch.
Sample:
<BoolProperty SwitchPrefix="/" ReverseSwitch="sdl-" Name="SDLCheck" Switch="sdl" ... />
Selecting Yes in property sheet will store it as <SDLCheck>
true</SDLCheck>
and renders it as /sdl
Selecting No in property sheet will store it as <SDLCheck>
false</SDLCheck>
and renders it as /sdl-
For more information see Microsoft.Build.Framework.XamlTypes.BoolProperty
EnumProperty Element
EnumProperty allows selecting one option from a set of possible values. It stores name of EnumValue
element when one of them is selected. Possible choices are defined by <EnumValue>
elements. EnumProperty
also defines AdmissibleValue
attribute to store acceptable values.
AdmissibleValues
Specifies the list of possible values for this property.
Sample:
<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>
Selecting first option in property sheet will store <WarningLevel>
TurnOffAllWarnings</
WarningLevel>
and renders it as /w0 in Command Line window.
Selecting second option stores <
WarningLevel>
Level1</
WarningLevel>
and renders it as /w1
Selecting last option stores <
WarningLevel>
EnableAllWarnings</
WarningLevel>
and renders it as /w1
For more information see Microsoft.Build.Framework.XamlTypes.EnumProperty
DynamicEnumProperty Element
DynamicEnumProperty
is very similar to EnumProperty
with only difference that Enum values are coming from dynamic enum provider. This property defines two attributes EnumProvider
and ProviderSettings
to specify provider data.
EnumProvider
Specifies the provider that produces the list of possible values for this property.
ProviderSettings
Specifies a provider-specific set of options to pass to the provider.
Sample:
<DynamicEnumProperty Name="PlatformToolset" DisplayName="Platform Toolset" EnumProvider="Toolsets" />
For more information see Microsoft.Build.Framework.XamlTypes.EnumProperty
For more in-depth coverage of these elements see Creating custom UI property pages/sheets in Visual Studio Part 2
History
03/03/2015 - Published.
03/09/2015 - Fixed few incorrect statements and added reference to Part 2