This article covers the extension of Visual Studio by integrating into its 'Settings' dialog pages. Option page registration and integration into the IDE for different kinds of extension packages will be examined, as well as the means to display various standard and user-created components inside a custom settings page. Also covered are the ways of accessing environment settings through Visual Studio Automation model and preservation mechanism for option pages.

This article is obsolete. You can read the new version of this article here.

Introduction

Visual Studio employs a single unified dialog window to provide an access to the settings of its various components. This window is available through the IDE Tools -> Options main menu item. A basic element of Visual Studio settings is an Options Page. The Options dialog window arranges its pages in a tree-like structure according to the membership of the pages in their respective functional groups. Each one of these pages could be uniquely identified by the name of its group and its own name. For example, Visual Basic source code editor settings page is "Text Editor, Basic".

Extension packages are able to access and modify the values of various settings from option pages registered in the IDE. They can also create and register their own custom options pages in the environment through the automation object model and MPF classes (Managed Package Framework, available only to VSPackage extensions). Visual Studio contains an embedded mechanism for preserving the state of its settings objects; it is enabled by default, but can be overridden or disabled.

Creating and registering user options pages

It can be useful for a Visual Studio extension plug-in to be associated with one or several custom options pages from the Tools->Options dialog window. Such tool for configuring an extension will conform to the environment's UI paradigm and is actually quite convenient for handling your extension's settings from within the IDE itself. The methods of implementing and integrating custom user options page into the IDE can vary, as they depend upon the type of the extension being developed and the technology being used (either an automation model or MPF).

Integrating settings through an MPF class

Managed Package Framework allows creating custom options pages by inheriting from the DialogPage class. As the environment loads each of its options pages independently when accessing the corresponding section of the Tools->Options dialog, each page must be implemented with an independent object as a result.

The object which implements a custom page should be associated with your VSPackage through the ProvideOptionPage attribute of the corresponding Package subclass.

[ProvideOptionPageAttribute(typeof(OptionsPageRegistration), "MyPackage", "MyOptionsPage", 113, 114, true)]

This attribute designates the names for the options page itself and for group that it belongs to, as it should be displayed in the IDE options dialog. A separate attribute should be used for every custom page that is to be integrated by the extension. In fact, this attribute is used to provide a registration for the page through pkgdef file and it does not directly affect the execution in any other way. For the user options page to be correctly rendered by the environment, the page should be registered in the following node of the system registry:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<VsVersion>\ ToolsOptionsPages

Here <VsVersion> is the version number of Visual Studio IDE, 10.0 for example. This record will be automatically created when ProvideOptionPage attribute is utilized. It should be noted that a correct uninstallation of an extension package also requires purging all of the records that this extension had written to the system registry before, including the ones belonging to its options pages. As the versions of Visual Studio IDE starting from 2010 can utilize VSIX packages to deploy/uninstall VSPackage plug-ins, the VSIX installer will automatically perform such registry operations according to its pkgdef file. But earlier versions of IDE may require manual registry cleaning, for instance by a stand-alone installer application.

The 6th bool-type argument of the attribute's constructor allows the user's custom options page to be registered as an automation object. This exposes the page to the Automation Object Model, providing an access to the its settings through the EnvDTE interfaces for other third-party plug-ins. Registering an automation object requires the creation of several records in the system registry (it is performed automatically when using the aforementioned attributes) in the following nodes:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<Version\Packages\ <PackageGUID>\Automation HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<Version>\ AutomationProperties

The ProvideProfile attribute allows registering an options page or any other independent object with the build-in mechanism for setting's state preservation, provided that such user object implements the IProfileManager interface.

Implementing an MPF DialogPage subclass

As a minimal requirement for DialogPage subclass to implement an IDE options page, this derived class should contain at least one public property. Here is an example of such a basic implementation:

namespace MyPackage { class MyOptionsPage : DialogPage { bool myOption = true; public bool MyOption { get { return this. myOption; } set { this. myOption = value; } } } }

To display such a generic implementation of DialogPage subclass, IDE will utilize a standard PropertyGrid control as a client area of the page window, which in turn will contain all public properties of this subclass. This could be convenient in case your extension's configuration properties are rather simple, so handling them through embedded PropertyGrid editors does not present any troubles. Using a control native to IDE will also exempt you from common issues with incorrect UI scaling for components on different DPI resolutions in the Visual Studio Options dialog.

However, if you want to host a user-created control inside an options page, it could be achieved by overriding the Window property of your DialogPage subclass:

[BrowsableAttribute(false)] protected override IWin32Window Window { get { return MyUserControl; } }

A reference to the IWin32Window object implementing the window's client area should be returned by this property. Visual Studio required its options pages to be constant, i.e. they should not be recreated for any of their subsequent calls. As Windows Forms objects can delete and recreate their window handles at will, it is recommend passing a reference to the object derived from a UserControl type.

The AutomationObject property of a custom options page derived from the DialogPage class determines the public properties which are shown by the default display mechanism and persisted by the IDE. AutomationObject returns a reference to the DialogPage subclass itself by default, but if it returns a reference to some other object, than the properties of that returned object will be preserved and displayed instead. By default, system registry serves as a local storage for state preservation mechanism. Overriding the DialogPage.SaveSettingsToStorage method makes it possible to change the way of object's state preservation method (similar could be done to the state restoration through LoadSettingsFromStorage override).

public override void SaveSettingsToStorage() { ... }

Custom pages registered as automation objects can store their settings, along with settings from other options pages, in an external XML file through the standard IDE command Tools -> Import/Export Settings, by the default implementation of the SaveSettingsToXml method which also can be overridden if necessary.

Of course, integrating a page into Visual Studio settings dialog is not the exclusive or mandatory way of creating configuration interface for an IDE plug-in. If the capabilities of a regular PropertyGrid are insufficient and there are no future plans to utilize the embedded mechanism for settings preservation, then it could be quite reasonable to implement an IDE independent settings dialog. The advantages of this approach are high portability (for instance, a plug-in that could be used with multiple IDEs) and complete control over the dialog window itself, which in turn substantially alleviates the support of various end-user configurations. On the downside, such solution makes your settings inaccessible to third-party developers through the automation object model.

For configuring its settings, PVS-Studio extension package utilizes a custom state preservation mechanism that operates through an external XML file, so that the options pages which the plug-in integrates into the IDE are provided only as means for displaying and modifying these internal settings. Initially, the embedded settings preservation functionality of Visual Studio created conflicts with PVS-Studio own settings mechanism in the earlier versions of the plug-in, leading to setting de-synchronization issues. This demonstrated us that even in the presence of an independent settings management inside the extension, it still may be necessary to override some of Visual Studio regular mechanisms (maybe even by an empty method).

Integrating settings through an Add-In xml definition

A user options page can be integrated into the IDE through an independent XML definition of an Add-In extension. The contents of such user page should be implemented as a user component, for example as an System.Windows.Forms.UserControl. This component is not associated with an Add-In itself, thus it can be implemented either inside the extension's assembly or as an independent library altogether. An add-in XML file could even be created for such user component alone, without any definitions for an Add-In extension. Let's examine an xml definition for an Add-In module which also contains a definition for a user's custom options page.

<?xml version="1.0" encoding="UTF-16" standalone="no"?> <Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility"> <HostApplication> <Name>Microsoft Visual Studio Macros</Name> <Version>10.0</Version> </HostApplication> <HostApplication> <Name>Microsoft Visual Studio</Name> <Version>10.0</Version> </HostApplication> <Addin> <FriendlyName>My Add in</FriendlyName> <Description>My Addin 1</Description> <Assembly>c:\MyAddIn1\MyAddin1.dll</Assembly> <FullClassName>MyAddin1.Connect</FullClassName> <LoadBehavior>0</LoadBehavior> <CommandPreload>1</CommandPreload> <CommandLineSafe>0</CommandLineSafe> </Addin> <ToolsOptionsPage> <Category Name="MyAddIn1"> <SubCategory Name="My Tools Options Page"> <Assembly> c:\MyAddIn1\MyAddin1.dll</Assembly> <FullClassName>MyAddin1.UserControl1</FullClassName> </SubCategory> </Category> </ToolsOptionsPage> </Extensibility>

A description for custom options page is located inside the <ToolsOptionsPage> node. The <Assembly> sub-node points to the library which contains a user component for the client area of the page. The <FullClassName> contains the full name of a user component in a Namespace.ClassName format. The <Category> and <SubCategory> nodes define position of a user page inside the Tools->Options tree-like structure by specifying page's group and personal names. Any existing group names, as well as a new one, can be used as a <Category> value. As evident by the example, a user MyAddin1.UserControl1 component is located inside the same assembly as the add-in itself, though this is not a mandatory requirement.

Visual Studio loads a page after it is opened by a user for the first time through the Options dialog window. As opposed to the integration of a page through the Managed Package Framework, the description of a page is stored within an xml description addin file, so the page will be initialized only after the environment discovers such a file. Visual Studio reads addin files which are available to it immediately after start-up. The Environment -> Add-In/Macross Security options page specifies the paths which are used for addin discovery. Contrary to custom option pages implemented through inheriting the MPF classes, such high level approach to the integration does not register such a page as an automation object, and so it does not provide ways to access the page's contents through automation object model or to utilize the embedded sate preservation mechanism of the environment.

Accessing option pages through the automation

Visual Studio Automation Object Model provides the means of accessing various system settings of the Tools->Options dialog , excluding some of the pages, such as 'Dynamic Help' and 'Fonts and Colors ' pages (they are available through separate APIs). User-created custom option pages are also available through the automation model in case they are registered as automation objects themselves (as described in the previous section).

The get_Properties method can be utilized to obtain the necessary settings:

Properties propertiesList = PVSStudio.DTE.get_Properties("MyPackage", "MyOptionsPage");

The option page can be identified by its own name and the name of group it belongs to. Here is the example of obtaining value for a specific property:

Property MyProp1 = propertiesList.Item("MyOption1");

The value of the property can be accessed and modified through the MyProp1.Value.

The ShowOptionPage method of the Package MPF subclass can be used to open and display the custom options page inside the Options window.

MyPackage.ShowOptionPage(typeof(MyOptionsPage));

As evident by the example, this method takes the type of a user-created DialogPage subclass. However, if it is required to open any other page which is not part or your extension project, a standard IDE page for example, then it could be located by its GUID identifier available at this registry branch:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\ ToolsOptionsPages\<OptionsPageNme>\

Here <OptionsPageName> is the name of a page inside the Tools -> Options dialog. Following is the example of opening a standard TextEditor -> General IDE settings page through the IMenuCommandService global service:

string targetGUID = "734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A"; var command = new CommandID(VSConstants.GUID_VSStandardCommandSet97, VSConstants.cmdidToolsOptions); var mcs = GetService(typeof(IMenuCommandService)) as MenuCommandService; mcs.GlobalInvoke(command, targetGUID);

In fact, this code is equivalent to the execution of the Tools.Options IDE command. It acn be invoked through the ExecuteCommand method of the EnvDTE.DTE:

dte.ExecuteCommand("Tools.Options", "734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A").

References

Other articles in this series