Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Extending Visual Studio’s Settings Dialogue

DZone's Guide to

Extending Visual Studio’s Settings Dialogue

·
Free Resource

About a year ago on our blog, we published several articles on the development of Visual Studio plugins in C#. We have recently revised those materials and added new sections and now invite you to have a look at the updated version of the manual as a series of articles here on DZone.

The other articles in the series can be found here:

This article (part five of the series) covers the extension of Visual Studio by integrating into its 'Settings' dialog pages. We examine option page registration and integration into the IDE for different kinds of extension packages, as well as the means to display various standard and user-created components inside a custom settings page. We also cover the ways of accessing environment settings through the Visual Studio Automation model and the preservation mechanism for option pages.

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 a 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

The Managed Package Framework allows for creating custom options pages by inheriting from the DialogPage. 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 the 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 a 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 the ProvideOptionPage attribute is utilized. It should be noted that a correct uninstallation of an extension package also requires purging of all 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 the IDE may require manual registry cleaning, for instance by a stand-alone installer application.

The sixth 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 access to 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 (this 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 built-in mechanism for the 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 the DialogPage subclass, the 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 the 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 requires 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, we 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 that 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, then the properties of that returned object would be preserved and displayed instead. By default, the system registry serves as a local storage for state preservation mechanisms. Overriding the DialogPage.SaveSettingsToStorage method makes it possible to change the object's state preservation method (similar could be done to the state restoration through the 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, and by the default implementation of the SaveSettingsToXml method that also can be overridden if necessary.

Of course, integrating a page into Visual Studio's settings dialog is not the exclusive or mandatory way of creating a 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 a solution makes your settings inaccessible to third-party developers through the automation object model.

For configuring its settings, the 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's own settings mechanism in the earlier versions of the plug-in, leading to setting de-synchronization issues. This demonstrated to 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's 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 a user page should be implemented as a user component, for example as a 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 a user component alone, without any definitions for an add-in extension. Let's examine an XML definition for an add-in module that 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 a 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 the position of a user page inside the Tools -> Options tree-like structure by specifying a 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 a user opens it 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 add-in file, so the page will be initialized only after the environment discovers such a file. Visual Studio reads add-in files that are available to it immediately after start-up. The Environment -> Add-in/Macros Security options page specifies the paths that are used for add-in discovery. Contrary to custom option pages implemented through inheriting the MPF classes, such a 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 an automation object model or to utilize the embedded state preservation mechanism of the environment.

Accessing Option Pages Through Automation

Visual Studio Automation Object Model provides the means for accessing various system settings in 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 ShowOptionsPage method of the Package MPF subclass can be used to open and display a 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 can be invoked through the ExecuteCommand method of the EnvDTE.DTE:

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

References

  1. MSDN. Options Pages.
  2. MSDN. State Persistence and the Visual Studio IDE.
  3. MSDN. User Settings and Options.
  4. MSDN. Registering Custom Options Pages.
  5. MSDN. Providing Automation for VSPackages.



Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}