Working with Microsoft Visual Studio 2005
Developers can be a finicky bunch—they want Visual Studio to work the way they want down to the finest detail; if even one option is set up in a way they didn't expect, they can become quite unproductive. The Options dialog box is full of options that you can configure—everything from how many spaces are inserted when the Tab key is pressed in the text editor to whether the status bar is shown along the bottom of the main window of Visual Studio.
Changing Existing Settings
Many settings in the Options dialog box can be controlled through the automation model by using the Properties and Property objects. To find a Properties collection, you must first calculate the category and subcategory of the settings you want to modify. On the left side of the dialog box is a tree view control that's rarely more than two levels deep. The top-level nodes in this tree, such as Environment, Source Control, and Text Editor, are the categories of options you can manipulate. Each category contains a group of related Options pages, and each page contains a number of controls you can manipulate to customize your programming environment. The subitem nodes are the subcategories of the Options dialog box; if you select one of these nodes, the right side of the Options dialog box changes to show the options for that category and subcategory. The category and subcategory used to find a Properties collection are based on the category and subcategory displayed in the Options dialog box user interface, but their names might be slightly different from the category and subcategory names. To find the list of categories and subcategories, you must use the Registry Editor. First, you find the item in the Options dialog box that you want to edit. For our example, we'll modify the tab indent size of the Visual Basic source code editor, which is found on the page of the Text Editor category and Basic subcategory.
Note | Note The Text Editor category is a bit different from other categories in the Options dialog box in that it has three levels, with the third level being a sub-subcategory. However, in the automation model, the General and Tabs sub-subcategories are combined into one and have the same name as they do in the programming language. |
After running regedit.exe, navigate to the key HKEY_ LOCAL_MACHINE\SOFTWARE\ Microsoft\VisualStudio\8.0\AutomationProperties. Underneath this key is a list of all the property categories accessible to a macro or an add-in. We're looking for the Text Editor category—the key whose name most closely matches this category name in the user interface is TextEditor (without a space). After expanding this item in the Registry Editor, you'll see a list of subcategories; one of those subcategories, Basic, matches the subcategory displayed in the user interface of the Tools Options dialog box, so this is the subcategory we'll use.
Now that we've found the automation category TextEditor and subcategory Basic, we can plug these values into the DTE.Properties property to retrieve the Properties collection:
Sub GetVBTextEditorProperties() Dim properties As Properties properties = DTE.Properties("TextEditor", "Basic") End Sub
The last step in retrieving a Property object is to call the Item method of the Properties collection. The Item method accepts as an argument the name of the property, but this name is not stored anywhere except within the object model. Remember that the Properties object is a collection, and, as with all other collection objects, it can be enumerated to find the objects it contains and the names of those objects. You can use the following macro to examine the names of what will be passed to the Properties.Item method. The macro walks all the categories and subcategories listed in the registry and then uses the enumerator of the Properties collection to find the name of the Property object contained in that collection. Each of these category, subcategory, and property names are then inserted into a text file that the macro creates:
Sub WalkPropertyNames() Dim categoryName As String Dim key As Microsoft.Win32.RegistryKey Dim newDocument As Document Dim selection As TextSelection 'Open a new document to store the information newDocument = DTE.ItemOperations.NewFile("General\Text File").Document selection = newDocument.Selection 'Open the registry key that holds the list of categories: key = Microsoft.Win32.Registry.LocalMachine key = key.OpenSubKey( _ "SOFTWARE\Microsoft\VisualStudio\8.0\AutomationProperties") 'Enumerate the categories: For Each categoryName In key.GetSubKeyNames() Dim subcategoryName As String selection.Insert(categoryName + vbLf) 'Enumerate the subcategories: For Each subcategoryName In _ key.OpenSubKey(categoryName).GetSubKeyNames() Dim properties As Properties Dim prop As [Property] selection.Insert(" " + subcategoryName + vbLf) Try 'Enumerate each property: properties = DTE.Properties(categoryName, subcategoryName) For Each prop In properties selection.Insert(" " + prop.Name + vbLf) Next Catch End Try Next Next End Sub
Using the output from this macro, we can find the TextEditor category and the Basic subcategory and then look in the Options dialog box for something that looks like the name Tab Size. The closest match is TabSize. Using this name, we can find the Property object for the Visual Basic text editor Tab Size:
Sub GetVBTabSizeProperty() Dim properties As Properties Dim prop As [Property] properties = DTE.Properties("TextEditor", "Basic") prop = properties.Item("TabSize") End Sub
Now all that's left to do is retrieve the value of this property by using the Property.Value property:
Sub GetVBTabSize() Dim properties As Properties properties = DTE.Properties("TextEditor", "Basic") MsgBox(properties.Item("TabSize").Value) End Sub
This macro displays the value 4, which is the same value in the Tools Options dialog box for the Tab Size option of the Basic subcategory of the Text Editor category. You set this value the same way you retrieve the value, except the Value property is written to rather than read:
Sub SetVBTabSize() Dim properties As Properties properties = DTE.Properties("TextEditor", "Basic") properties.Item("TabSize").Value = 4 End Sub
By simply changing the category and subcategories passed to the DTE.Properties property and looking at the list of property names generated by the WalkPropertyNames macro, you can modify many of the options shown in the Tools Options dialog box.
When you use the Visual Studio object model, you might use the Visual Basic Is operator or the .NET Framework Object.Equals method to try to determine whether two objects are the same. But the Is operator and the Equals method might not always return what you expect because of how the Visual Studio object model was built. If you run a macro such as this:
Sub CompareWindowsObjects() Dim window1 As Window Dim window2 As Window window1 = DTE.Windows.Item(Constants.vsWindowKindTaskList) window2 = DTE.Windows.Item(Constants.vsWindowKindTaskList) MsgBox(window1 Is window2) End Sub
a message box with the value True is displayed. When you ask for a Window object, the object model checks to see whether a Window object has been created for the specific window; if not, a new Window object is constructed and returned to the calling code. If a Window object has already been created, that object is recycled and returned to the caller. This is both a performance and memory consumption optimization because new objects are not unnecessarily created (which consumes memory) and initialized (which consumes processor time). But if you run code such as this:
Sub ComparePropertyObjects() Dim props1 As Properties Dim props2 As Properties props1 = DTE.Properties("Environment", "General") props2 = DTE.Properties("Environment", "General") MsgBox(props1 Is props2) End Sub
the message box displays False because the Properties collection must be reconstructed each time you call the DTE.Properties property to be sure it has the most up-to-date information.
Calling the DTE.Properties property multiple times can cause huge memory consumption problems. Suppose you call the DTE.Properties property repeatedly in a tight loop; every time the property is called, a new Properties collection is created, initialized, and then returned to the calling code. This object consumes memory for the COM object that Visual Studio creates, and if you're using a programming language supported by the .NET Framework, a .NET wrapper class that allows you to program this object is constructed. You can see the memory consumption grow almost boundlessly if you run the following macro and watch the vsmsvr.exe process (the process that hosts the instance of the .NET Framework and runs macro code) on the Processes tab of Windows Task Manager:
Sub RepeatedConstruct() Dim i As Long Dim props As Properties For i = 1 To Long.MaxValue props = DTE.Properties("Environment", "General") Next End Sub
When you run this macro, the loop never allows a garbage collection to occur because the .NET Framework is focused on running your code, not searching and removing unused objects. To make sure your program doesn't consume more memory than it should, you should be sure not to create more objects than necessary. You can do so by using the Is operator or the Object.Equals method and optimizing accordingly. For example, you can rewrite the RepeatedConstruct macro as follows and avoid system memory stress by simply moving the call to DTE.Properties outside of the loop:
Sub OptimizedRepeatedConstruct() Dim i As Long Dim props As Properties Dim showStatusbar As Boolean props = DTE.Properties("Environment", "General") For i = 1 To Long.MaxValue showStatusbar = props.Item("ShowStatusBar").Value Next End Sub
An unscientific measurement (consisting of opening Windows Task Manager and noting the amount of memory consumed before and after running the macro) shows that moving the one line outside of the loop saves almost 35 MB of memory—something your users will appreciate.
Creating Custom Settings
Not only can you examine and modify existing settings, but you can also create your own options for your add-ins. Creating a page in the Options dialog box for your add-in requires a .NET user control and creating an .addin file to let Visual Studio know how to load your Options page. The .addin file that you create for a tools options page does not necessarily need to contain the XML code to declare an add-in, but it can. When the user opens the Options dialog box, all the .addin files that can be found are opened, and, if the settings for a custom tools options page is found, the .NET user control is instantiated and shown in the Options dialog box.
Creating a tools options page is rather easy, especially with the CustomToolsOptionsPage starter kit included with the samples for this book. This starter kit will create both the .addin file and code for a user control that can be hosted on the Tools Options dialog box. Once you create this project, all you need to do is copy the .addin file and the .dll implementing the control into one of the special directories that Visual Studio looks for .addin files, and then start a new instance of Visual Studio. Let's look at the code that the starter kit will generate for you.
Declaring the XML for a Tools Options Page
The XML in the .addin file to declare a tools options page is quite simple; here is a snippet from an .addin file that declares a page:
<ToolsOptionsPage> <Category Name="My Custom Category"> <SubCategory Name="My Custom Subcategory"> <Assembly>MyCustomPage.dll</Assembly> <FullClassName>MyCustomPage.UserControl1</FullClassName> </SubCategory> </Category> </ToolsOptionsPage>
This snippet of XML declares a page that will create a node in the tree on the left side of the Tools Options dialog box named My Custom Category. It will also create a node under My Custom Category named My Custom Subcategory. If the user were to select this node, the assembly MyCustomPage.dll will be loaded, and then the class MyCustomPage.UserControl1 is instantiated and then displayed. If you used a name such as Environment as the category, this page is merged into the Environment node in the tree of the Tools Options dialog box. You can specify multiple SubCategory tags within a Category tag, and all those pages are grouped together under one top-level node.
The IDTToolsOptionsPage Interface
An Options page has three stages in its lifetime: creation, interaction, and dismissal. To allow your page to know about these three stages, the user control needs to implement the EnvDTE.IDTToolsOptionsPage interface. This interface has the following signature:
public interface IDTToolsOptionsPage { public void GetProperties(ref object PropertiesObject); public void OnAfterCreated(EnvDTE.DTE DTEObject); public void OnCancel(); public void OnHelp(); public void OnOK(); }
When the user first displays the Options dialog box, Visual Studio sees in the .addin file that you've declared a page, and it creates an instance of your .NET user control. If the IDTToolsOptionsPage interface is implemented on that control, the OnAfterCreated method is called and is passed the DTE object for the instance of Visual Studio that is creating the control. The implementation of this method can perform any initialization steps needed, such as reading values from the system registry and using these values to set up the user interface of the control.
The Options dialog box has three buttons the user can click: OK, Cancel, and Help. If the user clicks OK, the IDTToolsOptionsPage.OnOK method is called, giving your page a chance to store back into the system registry any values that the user might have selected. You should also perform any cleanup work in the OnOK method because the Options page is about to be dismissed. If the user clicks the Cancel button, the OnCancel method is called. No values that the user selected in the page should be persisted, and this method is called so you can perform any cleanup necessary because, as when the user clicks OK, the Options dialog box is about to be closed. If the user clicks Help, the OnHelp method is called, giving your page a chance to display any help necessary to the user. Unlike the other buttons, Help doesn't dismiss the dialog box, so you shouldn't do any cleanup or store or discard values during this method call.
The last method of the IDTToolsOptionsPage interface is the GetProperties method. This method allows users to retrieve a Properties object for the options on your page in the same way they could retrieve a Properties object for other Options pages.
Exposing a Property Object
As you saw earlier, many of the values in the Options dialog box are programmable through the Properties collection. You can also allow the user to set and retrieve the values of your page through the Properties collection by using the GetProperties method. This method returns a System.Object object instance, which is wrapped up into a Properties collection by Visual Studio and handed back to the user when the DTE.Properties property is called with the category and subcategory of your page. By default, the starter kit creates one property, called SampleProperty, and it returns a string with the value property value. You can change the value, type, and name of this property, and you can also add new properties. To modify or add properties, you need to change the interface PropertiesInterface that was generated by the starter kit, making any changes necessary, and then reflect those properties in the class PropertiesImplementation, which implements the PropertiesInterface interface. This interface and class set-up is necessary to allow Visual Studio to properly wrap the object and return a Properties object to the user. You also need to use two attributes to help Visual Studio create the Properties object. The first of these properties, ComVisibleAttribute, will expose the class as a COM object. Visual Studio uses a COM type library to inspect the properties object, and this is possible only with the ComVisibleAttribute being set to true. It is not necessary to set the Register for COM interop value in the project properties; the object needs just to be visible as a COM object. The second attribute, the ClassInterfaceAttribute attribute, adjusts how the type library information is exposed from the class implementing the properties, and it should look like this:
[System.Runtime.InteropServices.ClassInterface (System.Runtime.InteropServices.ClassInterfaceType.AutoDual)]
Of course, all these attributes are set up for you by the starter kit, so you should not need to make any of these changes yourself.
To find the property exposed by this object, you will pass the category and subcategory values from the .addin file to the DTE.Properties property and then use the returned Properties collection just as you would for a property built-in to Visual Studio. This macro will display the SampleProperty property for the XML snippet given earlier:
Sub ShowSamplePropertyValue() Dim props As Properties props = DTE.Properties("My Custom Category", "My Custom Subcategory") MsgBox(props.Item("SampleProperty").Value) End Sub
Категории