Inside Microsoft® Visual Studio® .NET 2003 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Inside Microsoft® Visual Studio® .NET 2003 [Electronic resources] - نسخه متنی

Brian Johnson

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید










The Options Dialog Box


Developers can be a finicky bunchthey want Visual Studio .NET 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 configureeverything 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 .NET.


Changing Existing Settings


Many settings in the Options dialog box can be controlled through the automation model 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, each containing 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 .NET source code editor, which is found on the page of the Text Editor category and Basic subcategory.

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 the programming language.

After running regedit.exe, you must navigate the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\AutomationP properties. 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 categorythe 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 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 and subcategory TextEditor and 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 like 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 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\7.1\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 .NET 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 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.



Is It What It Says It Is?


When you use the Visual Studio .NET object model, you might use the Visual Basic .NET 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 .NET 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 .NET 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 waste more memory than it should, you should be sure you're not creating more objects than necessary 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 up Windows Task Manager and noting of 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 memorysomething 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 an ActiveX control and making modifications to the system registry to let Visual Studio .NET know to load your Options page. An add-in is also required because Visual Studio .NET uses registry keys located under the registry keys for your add-in to find which Options pages are available. When the user opens the Options dialog box, the registry keys for each add-in is examined, and if the registry settings for a custom tools options page is found, the ActiveX control is instantiated and shown in the Options dialog box.

You can create the registry keys for a Options page by modifying the registry settings of the setup project created when you ran the Add-in Wizard. To make the necessary modifications, right-click on the setup project for your add-in in the Solution Explorer tool window and choose View | Registry. The Registry editor window for the setup project will open. Expand the tree view on the left side of this window until you find the ProgID key for the add-in you created using the wizard, and select that item. If you call the add-in you created using the wizardOptionsAddinand use all the default options except you select the Yes, Create A "Tools" Menu Item option, the left panel of the registry editor should look like that in Figure 10-7.


Figure 10-7. The Registry editor of an add-in called OptionsAddin



To declare an Options page, you must add three registry keys to this selected key, each one a child of the previous one added. The first key you create is called Options. As Visual Studio .NET displays the Options dialog box, every add-in's registry keys are examined and if the Options key is present, the Options dialog box knows that an add-in might have a Options page associated with it. To create the Options key, right-click on the registry key for the ProgID of the add-in, choose New | Key, and then type Options. The next two registry keys to create for the tools options are the category and subcategory of the Options page. To create the registry keys for the category and subcategory, simply repeat the steps you performed to create the Options key, with the category as a child of the Options key and the subcategory as a child of the category key. You can name the keys anything you want; the name that you use will be the text displayed in the Options dialog box tree. If you were to use the name Options Add-in for the category of your page and General for the subcategory, the registry editor should appear as shown in Figure 10-8.


Figure 10-8. The Registry editor of an add-in with the registry keys for an Options page



The last step in setting up the registry for an Options page is to declare the ProgID of the ActiveX control hosted in the Options dialog box when the appropriate category and subcategory are selected. To do this, select the subcategory node in the left side of the registry editor window, right-click on the right side of this window, choose New | String Value, and the type the name Control. To set this registry key's value, with the value Control selected, look in the Properties window and type the ProgID of the ActiveX control into the Value property. We haven't yet created the ActiveX control that we'll host, but if you're following along with this sample, enter the value OptionsAddinPage.OptionsAddinPageCtl. (This is the ProgID of the control that we'll soon create.)

Creating the ActiveX Control


When the registry editor of the setup project is populated with the registry keys necessary to declare the tools options page, the next step is to create the ActiveX control. This step is not as easy as it might seem. When we were trying to create a tool window using the Windows.CreateToolWindow method and we wanted to use a .NET Framework user control as the user interface for the window, we had to use a shim ActiveX control to host the user control because a .NET control is not an ActiveX control. The problem with using a user control as a Tools Options page is that, first, it is not an ActiveX control, and second, you cannot use the shim control because you need to be able to run code to tell the shim where to find the user control. Because Visual Studio .NET creates Tools Options pages for you instead of you creating it as you did with a tool window, code never has the chance to run to program the shim control.

For all these reasons, a .NET user control cannot be used as an Options page. That leaves the option of using an unmanaged programming language that can create ActiveX controlssuch as Visual C++. To create this control, you first add an ATL project to the project that contains your add-in; for this example, we'll call it OptionsAddinPage. Next, you implement an ActiveX composite control, hook up some additional interfaces, and write additional code to wire up the page. Because this is a book about programming Visual Studio .NET, we'll use our bag of tricks to create an Options page wizard. This wizard, OptionsPageWizard, will generate all the code necessary to create a Tools Options page for us. After building the wizard sample and copying the .vsz file into the Extensibility Projects folder, as you did in Chapter 9 for the WizardBuilder sample, you can right-click on the solution node in Solution Explorer, enter the project name OptionsAddin, and then run the wizard to create the starter code. The only option this wizard asks for is the ProgID of the add-in that the Options page is for so the correct location within the system registry to store options can be computed.

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, you can optionally implement the IDTToolsOptionsPage interface on the ActiveX control of your page. 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 .NET sees in the registry that you've declared a page, and it creates an instance of your ActiveX control. If the IDTToolsOptionsPage interface is implemented on that control, the OnAfterCreated method is created and is passed the DTE object for the instance of Visual Studio .NET 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 default page that the OptionsPageWizard generates does this very thingreading a value from the registry, and if set to a certain value, selecting the check box.

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. The code generated by the wizard stores the state of the check box into the registry at this time. 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 using the GetProperties method. This method returns an IDispatch interface, which is wrapped up into a Properties collection by Visual Studio .NET and handed back to the user when the DTE.Properties property is called with the category and subcategory of your page. By default, the OptionsPageWizard wizard creates one property, called CheckBoxOption, of the IDispatch interface. It corresponds to the check box on the user interface of the control. Using our example of the OptionsAddinPage page, you can call to this property using a macro such as this:


Sub GetSetCustomOptions()
Dim properties As EnvDTE.Properties
Dim prop As EnvDTE.Property
'Retrieve the Properties object of our custom page:
properties = DTE.Properties("Options Add-in", "General")
prop = properties.Item("CheckBoxOption")
'Display the options value:
MsgBox(prop.Value)
'Change the value, then display the new value:
prop.Value = True
MsgBox(prop.Value)
End Sub

In your add-in, when you need to retrieve the value of an option, you can use code such as that shown in GetSetCustomOptions to find how the add-in can act according to the user's preferences. For example, suppose you want to add a Tools Options page for the VSMediaPlayer tool window to allow the user to set a multimedia file that is played when the window is activated. You can use a line of code such as this to find that file path:


DTE.Properties("VSMediaPlayer", "General").Item("MediaFile").Value

It's important that you not cache the values you retrieve from a Property object because the user might modify these options at any time, and if you did not retrieve the value when the window became active, the cached file to play will be out-of-date.


/ 118