As you know, most of the windows in Visual Studio have an object model that you can use to program the contents and present data that your code generates. However, at times you might need to display data in a way that the existing tool windows cannot handle. To allow you to display data in a way that is most suitable for your add-in, the Visual Studio object model allows creation of custom tool windows.
There are two ways to create a tool window—one way is with an ActiveX control, and the other is with a .NET User Control. To create a tool window, all you need is an ActiveX control and an add-in that makes a call to the Windows.CreateToolWindow method. CreateToolWindow has the following method signature:
public EnvDTE.Window CreateToolWindow(EnvDTE.AddIn AddInInst, string ProgID, string Caption, string GuidPosition, ref object DocObj)
This method returns a Window object that behaves as any tool window that Visual Studio creates. Here are the arguments for this method:
AddInInst An add-in object that's the sponsor of the tool window. When the sponsor add-in is unloaded, all tool windows associated with that add-in are automatically closed and the ActiveX control is unloaded.
ProgID The ProgID of the ActiveX control that's hosted on the newly created tool window.
Caption The text to show in the title bar of the new tool window.
GuidPosition A GUID in string format. As you'll recall, the Windows.Item method can be indexed by a GUID, and that GUID uniquely identifies a specific window. The GUID assigned to your tool window and the GUID passed to the Windows.Item method are set by using this parameter. This GUID must be different from the GUID used by other tool windows;if you call CreateToolWindow multiple times, you must use a different GUID for each window.
DocObject Most ActiveX controls have a programmable object in the form of a COM IDispatch interface, which is mapped to a System.Object when you're using the .NET Framework. The programmable object of the ActiveX control is passed back to the caller through this parameter, which allows you to program the control as you would any other tool window. You can also retrieve the programmable object of the ActiveX control by calling the Object property of the Window object for the tool window that's created by using the CreateToolWindow method.
To demonstrate the use of the CreateToolWindow method, the sample files that accompany this book include an add-in project called VSMediaPlayer. This sample creates a tool window hosting the ActiveX control for Windows Media Player and then, by using the programmable object of the control, plays an audio file. The code that does the work of creating the tool window looks like this:
void CreateMediaPlayerToolWindow() { EnvDTE.Windows windows; EnvDTE.Window mediaPlayerWindow; object controlObject = null; string mediaPlayerProgID = "MediaPlayer.MediaPlayer"; string toolWindowCaption = "Windows Media Player"; string toolWindowGuid = "{}"; //Create and show a tool window that hosts the // Windows Media Player control: windows = applicationObject.Windows; mediaPlayerWindow = windows.CreateToolWindow(addInInstance, mediaPlayerProgID, toolWindowCaption, toolWindowGuid, ref controlObject); mediaPlayerWindow.Visible = true; //Play the Windows "Tada" sound: //Can get only the system directory (Eg: C:\windows\system32), // need to change this to the Windows install dir string mediaFile = System.Environment.GetFolderPath( System.Environment.SpecialFolder.System); mediaFile += "\\..\\media\\tada.wav"; MediaPlayer.IMediaPlayer2 mediaPlayer = (MediaPlayer.IMediaPlayer2)controlObject; mediaPlayer.AutoStart = true; mediaPlayer.FileName = mediaFile; }
The CreateMediaPlayerToolWindow method is called in two places in the sample add-in—once in the OnConnection method and once in the OnStartupComplete method. It must be called twice because of the way add-ins are loaded by Visual Studio. If an add-in is set to load on startup, when Visual Studio starts, the add-in starts loading. This loading process includes calling the OnConnection method. But the OnConnection method is called just before the Visual Studio main window is created and shown. If you call the CreateToolWindow method within OnConnection before the main window is shown, creating the tool window will fail because creating an ActiveX control requires its parent window to be visible. You can check to make sure that the main window has been created by examining the connectMode argument passed to the OnConnection method. If this is set to ext_cm_AfterStartup, the add-in was loaded through the Add-in Manager or by means other than the load on startup flag being set and Visual Studio being started. Therefore, the tool window can be shown when an add-in is loaded by using an OnConnection implementation such as this:
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom) { applicationObject = (_DTE)application; addInInstance = (AddIn)addInInst; //If the add-in is loaded from the Add-in Manager dialog, then // create and show the tool window: if(connectMode == Extensibility.ext_ConnectMode.ext_cm_AfterStartup) { CreateMediaPlayerToolWindow(); } }
If the load on startup flag is set and you want to show the tool window when an add-in is loaded, you can create the window in the OnStartupComplete method. This method is called when initialization of Visual Studio is complete, which includes creating and showing the main window. It's as simple as this code snippet:
public void OnStartupComplete(ref System.Array custom) { //If the add-in is loaded at startup, then // create and show the tool window: CreateMediaPlayerToolWindow(); }
To create a tool window by using a .NET user control, you will need to call the Windows2.CreateToolWindow2 method. This method has a signature that is similar to the Windows.CreateToolWindow method, but rather than taking a COM ProgID to identify a control, CreateToolWindow2 takes both a name of a .NET assembly and the full name of a class within that assembly that derives from the .NET Framework's System. Windows.Forms.UserControl class. The signature of the CreateToolWindow2 method is:
public EnvDTE.Window CreateToolWindow2(EnvDTE.AddIn AddInInst, string Assembly, string Class, string Caption, string GuidPosition, ref object DocObj)
The arguments that differ from the CreateToolWindow method are:
Assembly The name of an assembly. This can be either a full path to an assembly, such as C:\MyControlAssembly.dll, or a strong name for an assembly, such as Microsoft. VisualStudio.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f1 1d50a3a, processorArchitecture=MSIL, which is the name of the assembly implementing forms within the .NET Frameworks. The assembly name can also be a URL, meaning the prefix file:/// and even http:// can be used to locate the assembly.
Class The fully qualified name of a class implementing System.Windows.Forms. UserControl. The namespace as well as the class name must be given using the dotted format, such as System.Windows.Forms.PropertyGrid, which is the name for the property grid, the same grid used within the Properties window inside of Visual Studio.
Note | Note When using the CreateToolWindow or CreateToolWindow2 method, you should not use code such as Guid.NewGuid().ToString("B") to pass the GUID string. This code will create a new GUID each time the method is called. Rather, you should declare a variable at class scope such as string toolWindowGuid = "{}"; to create one GUID for each window type. If you ever need to find the Window object for the tool window again, you can locate that window directly with the DTE.Windows. Item(toolWindowGuid) method. Using the same GUID each time also will allow Visual Studio to save the position of the tool window wherever the user has placed the window, so the next time you create the tool window it can be placed back in the same position. You should use this GUID only for a tool window of the same type, and you should never use the same GUID to create multiple or different tool windows. |
The Form Layout sample uses the CreateToolWindow2 method to create its user interface. Because the user control implementing the user interface is contained within the same assembly as the add-in code, the assembly name can be found by using the Location property of the System.Reflection.Assembly class. This Assembly class is found with the GetExecutingAssembly static method, and Location returns the full path, with the file:/// protocol. Lastly, because the user control to be hosted is named FormLayoutCtl, and is contained within the FormLayout assembly, the class name passed to CreateToolWindow2 is FormLayout.FormLayoutCtl.
You can also create a programmable object for a tool window created from a .NET User Control. This will allow the users of your add-in to program your tool window, just as they program the Task List window or Toolbox. To expose a programmable object, all you need to do is to define an interface, and then implement that interface on the class that derives from System.Windows.Forms.UserControl. This bit of code shows an interface named IProgrammableObject, providing a programmable object on a control named UserControl1.
public interface IProgrammableObject { void Method(); } public partial class UserControl1 : UserControl, IProgrammableObject { public UserControl1() { InitializeComponent(); } public void Method() { System.Windows.Forms.MessageBox.Show("Method"); } }
With this code to implement a programmable object, you can create the tool window and call Method like this:
object obj = null; EnvDTE80.Windows2 window2 = (EnvDTE80.Windows2)applicationObject.Windows; string thisAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location; EnvDTE.Window win = window2.CreateToolWindow2 (addInInstance, thisAssembly, "MyAddin.UserControl1", "Window Caption", "{}", ref obj); win.Visible = true; IProgrammableObject programmableObj = (IProgrammableObject)obj; programmableObj.Method();
When two or more tool windows are tab-linked together, an image is displayed so the user can quickly recognize the tool windows that are linked together. To set the tab picture for a tool window that's created by an add-in, you use the Window.SetTabPicture method. SetTabPicture takes as its argument a COM IPictureDisp type, a bitmap handle, or a path to a bitmap file (a file with the extension .bmp) such as C:\somebitmap.bmp. To create an IPictureDisp object, you can use the same technique described earlier of calling the OleLoadPictureFile method and then passing the returned IPictureDisp object to the SetTabPicture method. Bitmap handles can be retrieved by loading a bitmap file by using any of the various ways that an image can be loaded into an instance of the .NET Framework's Bitmap class (such as from a resource embedded within the add-in's assembly), then the Bitmap.Handle property is called to retrieve the handle.
The bitmap to place onto a tool window tab must have one of two specific formats, and any deviation from these formats can cause the bitmap to appear with incorrect colors or not appear at all. The first format is for a 16-color bitmap, and it must be 16 by 16 pixels. If any portion of the bitmap is to appear transparent, the transparent pixels must have the RGB value 0,254,0. The format for this bitmap is the same format used for displaying custom pictures on command bar buttons; a bitmap can be shared for these two uses. The other format is for high-color bitmaps; it must use 24-bit color, and it, too, needs to be 16 by 16 pixels. If any portion of this bitmap is to appear transparent, the color to use is to have the RGB value 255,0,255.
You can call the Window.SetTabPicture method only on a tool window created by using the Windows.CreateToolWindow method. Windows defined by Visual Studio already have their bitmaps set; if you try to change them, an exception will be generated. If you want to set the bitmap for your own tool window, you should set it before setting the Visible property of your window to true; otherwise, the picture might not be displayed immediately. Lastly, if a custom picture is not set, Visual Studio uses a default picture—the Visual Studio logo.
As you select different windows in Visual Studio, you see the Properties window update itself with properties available for those windows. For example, if you select a file in Solution Explorer, a set of properties is made available—such as the file path, when the file was modified, or how the file should be built. When you create a tool window, you might also want to have properties for your tool window appear in the Properties window. You set items to appear in the Properties window by using the Window.SetSelectionContainer method, which takes as a parameter an array of type System.Object. These items are displayed in the Properties window when the window that has this method called on it becomes the active window. The sample VSMediaPlayerAdv, an extension to the VSMediaPlayer sample, displays a property set in the Properties window by calling the SetSelectionContainer method with the programmable object of Windows Media Player, which was returned through the DocObj parameter of the CreateToolWindow method. This portion of code shows how this is done:
object []propertiesWindowObjects = {mediaPlayer}; mediaPlayerWindow.SetSelectionContainer(ref propertiesWindowObjects);
You can call the SetSelectionContainer method only on tool windows that you create. If you call this method on a Window object for, say, the Solution Explorer tool window, an exception will be generated.