8.1 The VS.NET Automation Object ModelThe IDE exposes an object model that allows you to automate many of the tasks that would normally be done manually. The same object model is used by macros, add-ins, and wizards. (Wizards are discussed in the next chapter.) At the core of the object model is the DTE object. (DTE stands for Development Tools Extensibility. Technically the object's coclass is DTE, and it implements an interface named _DTE, with an underscore. However, this COM-level detail will be hidden from you if you are working with VB.NET.) This object is the gateway into all of the functionality of the IDE.
The way in which you obtain a reference to the DTE object will depend on what type of code you are writing. Macros just use a global variable provided by the macro environment called DTE. Add-ins are passed a reference to this object when VS.NET initializes them. (Wizards, which are discussed in the next chapter, also have access to the DTE object in their script files through a global object called dte.) The best way to get a feel for what functionality is available from the DTE object model is to look at the properties available from the DTE object. Table 8-1 lists these properties and shows which sections of this chapter provide further information about the areas of functionality to which the various properties belong.
The DTE model provides access to many different aspects of VS.NET—some of the objects deal with solutions and projects, some deal with the VS.NET user interface, some deal with source control, and some deal with settings. The most important groups of objects are described in the following sections. 8.1.1 Solution and Project ObjectsAs Figure 8-1 illustrates, the DTE model provides an object hierarchy that mirrors the hierarchy of a solution and its projects in the IDE. The DTE object represents the IDE (VS.NET itself), and it has a Solution property, which is an object that represents the currently loaded solution. The Solution object contains a collection of Project objects, one for each project in the solution. Each Project contains a collection of ProjectItem objects that represent the files in the project. Each object in the hierarchy exposes methods and properties that allow you to carry out actions that you would normally perform interactively in the IDE. For example, the Solution object has a Remove method that allows you to remove a project from the solution. This method is the programmatic equivalent to right-clicking on the project in the Solution Explorer and selecting Remove. Figure 8-1. Solutions, projects, and files in the DTE object modelExample 8-1 shows how to iterate through all of the items in each project in a solution using C#. (This snippet presumes that there is a field or variable in scope called DTE that contains a reference to the DTE object. Macros have such a property available globally. Add-ins are passed the DTE object during initialization.) Example 8-1. Iterating through project itemsSolution s = DTE.Solution; foreach(Project p in s.Projects) { foreach(ProjectItem pi in p.ProjectItems) { MessageBox.Show("Item {0} in {1} Project of the {2} Solution", pi.Name, p.Name,s.FullName); } } 8.1.1.1 Project objects and VSProjectAlthough all project types have a great deal in common, there are certain features found only in .NET projects. For example, a .NET project has a list of references to other .NET components, but a Database project would have no use for such settings. To accommodate project-specific functionality, the Project object has a property called Object through which extra features are exposed, when appropriate. VS.NET uses this facility with .NET projects to provide an object of type VSProject. You can retrieve a VSProject object like this: Imports EnvDTE Imports VSLangProj ... Dim project As Project project = DTE.Solution.Projects.Item(1) Dim vsProject As VSProject vsProject = project.Object Note the Import statements—most of the VS.NET object model is defined in the EnvDTE namespace, but here we also need to import the VSLangProj namespace, as this is where VSProject is defined.
The VSProject object provides a References property, which is a collection of Reference objects, one for each reference the project has. It also has a WebReferencesFolder property for web service references. It provides a WorkOffline property, which allows you to work on web projects in a disconnected environment. It also provides a couple of utility methods for managing web service references. 8.1.1.2 Properties collectionsMany of the entities you deal with in VS.NET have properties associated with them. Solutions, configurations, projects, and files all present property sets either in the Properties panel (F4) or the Property Pages dialog (Shift-F4). Properties present a challenge because the exact set of properties available can vary—for example, a Project object's properties will depend on the type of project. Although in certain special cases this is dealt with by introducing an extra object such as the VSProject object described earlier, the DTE object model has a more extensible way of dealing with properties. All objects that represent items with property sets have a property called Properties. This is a collection of Property objects and is indexed by the name of the property. The set of properties available depends on the type of object—the VS.NET documentation provides the full (and extensive) lists for each type. Example 8-2 shows how to use this feature to retrieve the DefaultNamespace property that is present on C#, J#, and VB.NET projects. Example 8-2. Retrieving a project propertyPublic Function GetNamespace(proj As Project) As String Dim prop As [Property] prop = proj.Properties.Item("DefaultNamespace") Return prop.Value End Function
The DTE object itself also has a Properties property, but it works slightly differently. It contains systemwide settings, as configured in the Options dialog (Tools Options). But unlike the other Properties properties, this one is not a collection object. Instead, it is a parameterized property that takes two strings, a Category and a Page. These mostly correspond to the Options dialog's categories and pages. For example, you can access the settings in the Environment category's General page with DTE.Properties("Environment", "General"). However, there are a few documented anomalies. For example, although the Fonts and Colors page is in the Environment category, you must use DTE.Properties("FontsAndColors", "TextEditor") to access these settings. 8.1.2 User Interface ObjectsThe DTE object model has two main kinds of objects that represent user interface elements: Window objects and CommandBar objects. Window objects represent windows, such as document editor windows, the Toolbox, the Solution Explorer, the Breakpoint window, and so on. CommandBar objects represent menu bars and toolbars, such as the main menu. 8.1.2.1 Window objectsFor each visible window, whether it is the main VS.NET window, a document window, or a tool window, there is a corresponding Window object available in the DTE object hierarchy. You can obtain these objects in a number of ways. The DTE object itself provides two properties that provide direct access to certain windows. Its MainWindow property refers to the main VS.NET window. The ActiveWindow property refers to whichever window currently has the input focus. The DTE object also provides a Windows property. This is a collection of Window objects and allows access to every window in the VS.NET UI. The property is indexed by the window kind, which is a GUID that indicates the type of window. This GUID would normally be one of those listed in the DTE's Constants enumeration, which defines a series of vsWindowKindXxx values for the built-in window types. (The documentation page entitled "vsWindowKind constants" provides the full list of built-in windows and their corresponding vsWindowKind names.) Example 8-3 shows how to use this collection to obtain the Window object for the Solution Explorer. Example 8-3. Obtaining a particular Window objectDim wnd As Window wnd = DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer)
Once you have a Window object, you can perform various operations on it. As you would expect, anything that can be done interactively can also be done through code. The AutoHides property determines whether the window disappears when it loses the focus—this corresponds to the pushpin icon on the window. The IsFloating property determines whether the window is currently docked. The Top, Left, Width, and Height properties allow the window's size and position to be set when it is undocked. The Visible property determines whether it is shown at all. The Activate method gives the window the focus. If the window is an editor window, you can access the associated document through its Document property. Certain window types provide an extra programming interface, which is available from the Window object's Object property. All of the windows that show a tree view (e.g., the Solution Explorer or the class view) use this to provide an object of type UIHierarchy. UIHierarchy objects provide a GetItem method that allows access to any item in the tree. It also provides SelectUp and SelectDown methods for navigation and a DoDefaultAction method to allow a double-click to be simulated. Example 8-4 shows the use of the UIHierarchy object. It obtains the Window object for the Solution Explorer and then retrieves the UIHierarchy object. It then calls GetItem on this to retrieve the item representing the MyProject project in the MySolution solution. It then calls Select on this, in order to make that the currently selected item. Example 8-4. Using the UIHierarchy objectDim wnd As Window wnd = DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer) Dim uih As UIHierarchy uih = wnd.Object Dim uihItem as UIHierarchyItem uihItem = uih.GetItem("MySolution\MyProject") uihItem.Select(vsUISelectionType.vsUISelectionTypeSelect) 8.1.2.2 CommandBar objectsCommandBar objects represent menus or toolbars. There is no distinction between a menu bar and a toolbar—buttons can be dragged onto the menu bar, and menu items can be dragged onto button bars.
The DTE object has a CommandBars property. This is a collection that contains every command bar in the VS.NET UI. (It includes any that are currently invisible, as well as all the visible ones.) The collection is indexed by the name of the command bars. It also provides an Add method that allows you to create new command bars. CommandBar objects provide various properties that let you control their appearance and contents. Example 8-5 shows how to locate the Standard command bar (one of the built-in VS.NET toolbars) from the DTE object's CommandBars collection. It then toggles the bar's position between being docked to the top of the screen and floating. Example 8-5. Changing a command bar's positionImports Microsoft.Office.Core Public Module MyModule Public Sub AddToolbar( ) Dim cmdBar As CommandBar cmdBar = DTE.CommandBars.Item("Standard") If cmdBar.Position = MsoBarPosition.msoBarTop Then cmdBar.Position = MsoBarPosition.msoBarFloating Else cmdBar.Position = MsoBarPosition.msoBarTop End If End Sub End Module The most interesting property of any CommandBar object is the Controls property. This is a collection of CommandBarControl objects, one for each item on the bar. There are several different types of control. You can find out which type any particular control is from its Type property, which will return an item from the msoControlType enumeration. Menus have a type of msoControlPopup, and the objects that represent menus can be cast to the CommandBarPopup type. Leafs in a menu and buttons on a toolbar both have the type msoControlButton. Objects in the bar's Controls collection that have this type can be cast to the CommandBarButton type. Example 8-6 shows how to navigate through a tree of pop ups in a command bar—in this case we are using the main menu in VS.NET, which is a command bar called "MenuBar". Example 8-6 locates the File menu and then the Source Control submenu, before executing the Open from Source Control... menu item. Example 8-6. Navigating through controls in a menuImports Microsoft.Office.Core Public Module MyModule Public Sub UseCommandbar( ) Dim cmdBar As CommandBar Dim ctl As CommandBarControl Dim cmdPopup As CommandBarPopup Dim cmdButton As CommandBarButton cmdBar = DTE.CommandBars.Item("MenuBar") ctl = cmdBar.Controls("File") If ctl.Type = MsoControlType.msoControlPopup Then cmdPopup = ctl ctl = cmdPopup.Controls("Source Control") If ctl.Type = MsoControlType.msoControlPopup Then cmdPopup = ctl ctl = cmdPopup.Controls("Open From Source Control...") If ctl.Type = MsoControlType.msoControlButton Then cmdButton = ctl cmdButton.Execute( ) End If End If End If End Sub End Module In fact, this code is unnecessarily complex—navigating through toolbars is required only if you wish to modify them in some way. If you merely wish to execute the command they represent, you should just use the corresponding Command object. You also need to use a Command object if you want to add an item to a command bar that actually does something—a command bar button must be associated with the command that it invokes. 8.1.3 Command ObjectsMost user actions in VS.NET are associated with a command. There are commands for every action in the editor, such as entering text or moving the cursor. Each dialog has a command that opens it. Every action accessible through toolbars and menus is associated with a command.
Every command has a corresponding Command object, which can be obtained through the DTE object's Commands collection. Commands are identified by name, available from the Command object's Name property. This name can also be used to invoke a command with the DTE object's ExecuteCommand method. Example 8-7 shows the more succinct way of invoking the same command that Example 8-6 executes. Example 8-7. Executing a commandDTE.ExecuteCommand("File.OpenFromSourceControl") If you want to add an item to a toolbar menu that invokes a particular command, you simply obtain the relevant command object and call its AddControl method, passing in a reference to the command bar to which you would like to add a control. Example 8-8 shows how to add a button for the OpenFromSourceControl command as the fourth item in the Standard toolbar. Example 8-8. Adding a command to a command barDim cmd As Command cmd = DTE.Commands.Item("File.OpenFromSourceControl") cmd.AddControl(DTE.CommandBars("Standard"), 4) You can create your own custom command objects, although you will need to write an add-in to provide code that will run when the command is executed. This is done with the DTE object's Commands collection, which has an AddNamedCommand method. This allows you to create a command, specifying the name, the text that should be used for this command on command bars, optional tooltip text, and the bitmap that should be used to represent the command on any command bar. The VS.NET Add-in Wizard described later in this chapter can generate code to add a new command and attach it to the Tools menu for you. 8.1.4 Document ObjectsEvery document open for editing in VS.NET has a corresponding Document object, which allows the document's contents to be manipulated. If the document is a text file, the Document object's Object property will return a TextDocument object, which provides operations specific to text files. The DTE object provides two properties through which you can obtain a Document object. The ActiveDocument property returns the document that has the focus (or, if a tool window currently has the focus, the document that most recently had the focus). The Documents property is a collection of all open documents. Most manipulation of a document is done through the document's Selection property. For a text document, this will be a TextSelection object. This represents the current selection, or, if there is no selection, the cursor location. It provides methods equivalent to the keystrokes for navigating around documents—for example, the LineUp, LineDown, CharLeft, CharRight, PageUp, PageDown, StartOfDocument, and EndOfDocument methods. Each of these takes a Boolean indicating whether the operation should extend the current selection or not. (This is equivalent to whether or not you hold down the Shift key when using the corresponding keystroke.) An Insert method inserts text at the current cursor location. Cut, Copy, and Paste methods correspond to the standard clipboard operations. 8.1.5 Debugger ObjectThe DTE object provides a property called Debugger. This is an object that allows the debugger to be controlled. This provides a Breakpoints collection, allowing breakpoints to be created, destroyed, or modified. For multiprocess and multithreaded debugging, it allows the current process and thread to be retrieved or set using the CurrentProcess and CurrentThread properties. It provides methods that correspond to each of the debugger actions. (See Chapter 3 for more information on debugging.) Example 8-9 shows how to use the Debugger object to step into the current line of code. Example 8-9. Using the Debugger objectDim dbg As EnvDTE.Debugger dbg = DTE.Debugger dbg.StepInto( ) 8.1.6 Source Control ObjectThe DTE object provides a SourceControl property. This is an object that allows certain source control operations to be performed. Unfortunately, it is fairly primitive. All operations use filenames—you cannot pass a ProjectItem object in, for example. And you cannot check items in—you can only perform four source control operations. You can discover whether items are under source control at all with the IsItemUnderSCC method. You can call the IsItemCheckedOut method to discover whether an item is already checked out. You can exclude items from source control with ExcludeItem or ExcludeItems. And you can check items out with the CheckoutItem or CheckoutItems methods. 8.1.7 DTE EventsThe DTE object model is able to notify us when certain events happen. These events are raised through the standard COM notification mechanism (connection points). Events are grouped into categories, and as Figure 8-2 shows, each category has a corresponding event source object. (The objects shown with bold names are event sources. The other objects indicate how to navigate through the DTE object hierarchy to find the event sources.) Most of these objects are accessed through the DTE object's Events property. For example, build events are raised by the DTE.Events.BuildEvents object. Figure 8-2. DTE event objectsVSProject objects supply extra events specific to .NET projects through their VSProjectEvents objects. (VSProject objects are available on .NET projects, and are accessed through the associated Project object's Object property. Project objects can be accessed through the DTE.Solution.Projects collection.) These projects also provide project-specific events for individual items through the VSProjectItemEvents objects. Add-ins can use normal COM event handling to deal with events from these objects, but macros must use their own technique. This is discussed in the next section, Section 8.2; see Example 8-13 for an illustration of the technique. |