Window Basics
The user interface for each window in Visual Studio .NET is different from that of other windows, but they all share a few basic methods and properties. Let's look at the common parts of the object model.
The Windows Collection
Visual Studio .NET contains a number of tool and document windows that you can access through the automation model. Each of these windows is represented in the object model by a Window object and can be found in the Windows collection, which is accessible through the DTE.Windows property.You can retrieve a Window object from the Windows collection in a number of ways. One way is to use the enumerator to walk the list of all available windows, as shown here:
Sub EnumWindows()
Dim window As EnvDTE.Window
For Each window In DTE.Windows
MsgBox(window.Caption)
Next
End Sub
Or you can use the numerical indexing method:
Sub EnumWindows2()
Dim window As EnvDTE.Window
Dim i As Integer
For i = 1 To DTE.Windows.Count
MsgBox(DTE.Windows.Item(1).Caption)
Next
End Sub
However, using these formats for finding a window isn't optimal because you usually want to find one specific window, and looking at all the windows to find it is a waste of CPU cycles. The numerical indexing method isn't always best because the position of a window from one instance of Visual Studio.NET to the next might change, so you can't rely on using an index to return a specific Window object. In fact, you have no guarantee that calling the Item method two times in a row using a numerical index will return the same EnvDTE.Window object because new windows might be created in between calls to this method. In addition, the numerical indexing method might not find all the available windows. For example, creating a tool window can be an expensive operation. To increase performance, Visual Studio .NET won't create a tool window until one is specifically asked for, and because the numerical indexing method looks only for windows that have been created, a particular tool window might not be found.A simple experiment shows how enumerating through the list of all tool windows slows down your code if all tool windows haven't been created. By default, the Server Explorer tool window is docked and hidden on the left side of the Visual Studio .NET main window. If you move the mouse pointer over the icon for this window, the Server Explorer window appears. If this window hasn't yet been shown for that instance of Visual Studio .NET, you'll see a couple-second delay as the window is created before being shown for the first time. If you run the EnumWindows2 macro and some of the Window objects need to be created, creating those windows will consume a lot of processor time, causing the macro to run very slowly.Another way to find a window is to index the Windows collection by using the name of the window. The following macro demonstrates this approach; it uses the name of the Task List tool window to find the Window object for the Task List.
Sub FindTaskListWindow()
Dim objWindow As EnvDTE.Window
objWindow = DTE.Windows.Item("Task List")
End Sub
This is also not the best way of finding a particular Window object, as this example clearly shows. During a search for a window, the string passed to the Windows.Item method is compared to the title of each window until a window with a matching title is found. If you right-click on the Task List and choose Show Tasks | Comment, the title of this window becomes "Task List X Comment tasks shown (filtered)," where X is a number. Because the string "Task List" passed to the Item method doesn't exactly match the title of the Task List window, the code Windows.Item("Task List") won't find the Window object. This isn't to say you can't use the title indexing method in some situations. Some windows, such as the Properties window or Object Browser window, have names that don't change (unless the user is using a different language), and finding such windows by using the window title as the index works. Another reason why passing the title of a window isn't the best choice for the Item method is because, just as in the case of a numerical index, if the tool window hasn't been created, the Window object won't be found.The best way to find a Window object is to use an index that is unique and independent of both the position within the Windows collection and the title of the window. Each tool window has a constant GUID assigned to it; you can pass this GUID to the Item method to find the window you need. Because a GUID might be hard to remember, most of the tool windows that Visual Studio .NET can create have constants defined that are easier to remember and recognize. These constants all start with the prefix vsWindowKind and are static (shared if you're using the Visual Basic .NET language) members of the EnvDTE.Constants class. The following macro finds the Task List tool window:
Sub FindTaskListWindow2()
Dim objWindow As EnvDTE.Window
objWindow = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindTaskList)
End Sub
Because the GUID is unique to a specific tool window and doesn't change over time, you don't need to worry about either the caption of a window or its position within the EnvDTE.Windows collection changing. One other benefit of using the GUID is that even if the window you're searching for hasn't yet been created, Visual Studio .NET is smart enough to create the tool window when it's asked for.You might occasionally run across a window that doesn't have a constant GUID defined for it. The Favorites window is an example. When you need such a window, you can use the GUID in the form of a string in place of one of the predefined constants, as shown in the following example, which retrieves the Window object for the Favorites window:
Sub FindTheFavoritesWindow()
Dim window As EnvDTE.Window
window = DTE.Windows.Item("{E8B06F43-6D01-11D2-AA7D-00C04F990343}")
End Sub
You can find the GUID that can be passed to the Item method by using the ObjectKind property. The following macro takes this approach to display the GUID for the Favorites window:
Sub FindTheFavoritesWindow2()
Dim window As EnvDTE.Window
'You should show the Favorites window
' before calling this code!
window = DTE.Windows.Item("Favorites")
MsgBox(window.ObjectKind)
End Sub
When you run this macro, the GUID for the Favorites window is displayed in a message box. You can then define a constant set to this GUID, and use this constant in any code that needs to find this window. This is how we found the GUID for the FindTheFavoritesWindow macro.
Using the Object Property
Many windows in Visual Studio .NET have an object model that you can use to manipulate the data contained in that window. You can find these window-specific objects using the Object property of the Window object. For example, calling the Object property of the Window object for the Task List window returns the TaskList object, which allows you to enumerate, add, and change properties of task items in the Task List window. The following macro retrieves the TaskList object:
Sub GetTaskListObject()
Dim window As EnvDTE.Window
Dim taskList As EnvDTE.TaskList
window = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindTaskList)
taskList = CType(window.Object, EnvDTE.TaskList)
End Sub
A number of types are available as the programmable object for the different windows, not just the TaskList object, as shown in the macro. Table 10-1 lists the GUID constant you pass to the Item method to find a Window object, as well as the programmable object for that window.
Window | GUID Constant | Object Type |
---|---|---|
Command Window | vsWindowKindCommandWindow | EnvDTE.CommandWindow |
Macro Explorer | vsWindowKindMacroExplorer | EnvDTE.UIHierarchy |
Output window | vsWindowKindOutput | EnvDTE.OutputWindow |
Server Explorer | vsWindowKindServerExplorer | EnvDTE.UIHierarchy |
Solution Explorer | vsWindowKindSolutionExplorer | EnvDTE.UIHierarchy |
Task List | vsWindowKindTaskList | EnvDTE.TaskList |
Toolbox | vsWindowKindToolbox | EnvDTE.ToolBox |
Web browser window | vsWindowKindWebBrowser | SHDocVw.WebBrowser |
Text editor | <None> | EnvDTE.TextWindow |
Forms designer | <None> | System.ComponentModel.Design.IDesignerHost |
HTML designer | <None> | EnvDTE.HTMLWindow |
The Main Window
Each tool and document window in Visual Studio .NET has a Window object available. However, Visual Studio .NET is also a window, so it's only fair that a Window object be available for that window as well. Rather than indexing the EnvDTE.Windows collection to find this Window object, you use the MainWindow property of the DTE object:
Sub FindTheMainWindow()
Dim mainWindow As EnvDTE.Window
mainWindow = DTE.MainWindow
End Sub
When you work with the Window object for the Visual Studio .NET main window, a few methods and properties don't work as they do when you work with tool or document Window objects. The differences between tool and document Window objects and the Window object for the main window are as follows:The Document, Selection, Object, ProjectItem, and Project methods return null if you're using C#, and they return Nothing if you're using Visual Basic .NET.The set versions of the Caption and Linkable properties generate an exception if called. IsFloating and AutoHides generate an exception if you call the get or set versions of these properties.The Close method generates an exception if called.
A number of methods and properties don't work on the Window object for the main window, and one property is available only for the main window. If an add-in or a macro needs to display a dialog box, you should supply a parent window when the dialog box is shown to correctly manage focus and set the "modalness" of the new window. You can use the main Visual Studio .NET window as the parent window by using the Window.HWnd property. This property returns a handle to a windowa Windows platform SDK HWND data type. This property is hidden, so when you develop your add-in or macro it doesn't appear within statement completion. Because the .NET Framework can't use HWND values as a parent, this handle must first be wrapped by a class that implements an interface that the .NET library can accept as a parent. You can implement this interface, System.Windows.Forms.IWin32Window, on your add-in class or on a separate class within a macro project. The IWin32Window interface has one property named Handle; this property returns a System.IntPtr, which contains the handle to a parent window and, in this case, is the value returned from the Window.HWnd property. When it's time to show a form using the Form.ShowDialog method, you can pass the class that implements the IWin32Window as an argument to this method.To implement IWin32Window for an add-in, you must first add it to the interface list for your add-in, as shown here:
public class Connect : Object, Extensibility.IDTExtensibility2,
System.Windows.Forms.IWin32Window
Next, you add the implementation of the Handle property:
//Implementation of the IWin32Window.Handle property:
public System.IntPtr Handle
{
get
{
return new System.IntPtr (applicationObject.MainWindow.HWnd);
}
}
Finally, you can display the form (assuming that a form class named Form1 exists within an add-in project) using code such as this:
Form1 form1 = new Form1();
form1.ShowDialog(this);
Implementing this interface within a macro is even easier; the macro samples project that is installed with Visual Studio .NET already contains the code for a class that implements this interface. Located in the Utilities module of the Samples project, this class, named WinWrapper, can be instantiated and passed to any code that requires a parent window, such as the standard Open File dialog box:
Sub ShowFileOpenDialog()
Dim openFile As New OpenFileDialog
openFile.ShowDialog(New WinWrapper)
End Sub
All you do is copy the WinWrapper class into your macro project, and it's ready to use.