In the previous post we started to set up our sample: we added a command menu function to an empty package while discovering the Visual Studio Command Table file, its role and usage.
In this post we go on with this sample and add a tool window by hand.
Add the tool window to the project
We are going to create the following simple tool window:

The functionality of our tool window is quite simple: We put numbers into the FirstArgEdit and SecondArgEdit text boxes and select an operation (+, - , *, / or %) in OperatorCombo. By clicking on Calculate we get the result in the ResultEdit text box.
To create and integrate our tool window into the StartupToolset sample, we have a TODO list with the following steps:
Step 1: Design the user interface of the tool window
Step 2: Implement the functionality of the tool window
Step 3: Setup the resources to be used by the tool window
Step 4: Create a ToolWindowPane class to integrate the user interface into the IDE
Step 5: Associate the tool window with your package
Step 6: Write code to display the tool window
In Part #4 we have already added a tool window to a package.
As we saw, we need at least two types to create a tool window. The first type is a WinForms user control representing the UI (and in simple cases the functionality) of the tool window. The second type is a class deriving from ToolWindowPane to embed the tool window UI into the Visual Studio IDE.
Step1: Designing the user interface
Add a new user control to the StartupToolset project with CalculateControl.cs name. Place the appropriate controls from the Toolbox to the design surface with the names in the figure above. Set the anchoring of ResultEdit to [Top, Left, Right]. Set the style of OperationCombo to DropDownList and add “+”, “-“, “*”, “/”, “%” to its items.
Step 2: Implementing the tool window functions
We have many ways (design patterns) to implement the functionality of a tool window. If it covers complex task, we create collaborating types that share the responsibility of those tasks. With VSPackages we can decide to create services and allow our package and other packages to co-operate with.
However, in this article we make the simplest form: we add the implementation code directly to the user control class representing the tool window user interface.
Add event handlers for the Load event of the CalculationControl class and for the Click event of the CalculateButton and set up them as in the following code:
using System;
using System.Windows.Forms;
namespace MyCompany.StartupToolset
{
public partial class CalculationControl : UserControl
{
public CalculationControl()
{
InitializeComponent();
}
private void CalculationControl_Load(object sender, EventArgs e)
{
OperatorCombo.SelectedIndex = 0;
FirstArgEdit.Text = "0";
}
private void CalculateButton_Click(object sender, EventArgs e)
{
try
{
int firstArg = Int32.Parse(FirstArgEdit.Text);
int secondArg = Int32.Parse(SecondArgEdit.Text);
int result = 0;
switch (OperatorCombo.Text)
{
case "+":
result = firstArg + secondArg;
break;
case "-":
result = firstArg - secondArg;
break;
case "*":
result = firstArg * secondArg;
break;
case "/":
result = firstArg / secondArg;
break;
case "%":
result = firstArg % secondArg;
break;
}
ResultBox.Text = result.ToString();
}
catch (SystemException)
{
ResultBox.Text = "#Error";
}
}
}
}
I suppose, the code is simple enough not to explain its details.
Step 3: Setting up resources
When our tool window is displayed, Visual Studio IDE can display a bitmap on the window tab associated with the tool window. For example, when our Calculate Tool Window is displayed in the same window frame as Solution Explorer, you can see the bitmap on the tab:

We cannot explicitly create a bitmap and pass it to the tool window. We must do it using resources: we can pass only resource identifiers when initializing the tool window. Because those resources are handled by the VS IDE, they must be within the VSPackage.resx file.
To prepare the “clock” bitmap for the tool window, add the bitmap file to the VSPackage.resx with an integer resource ID, let’s say 300.
We may use resources in a package that are handled by our code (and not by the IDE). The best place for these resources is the Resources.resx file, since Visual Studio automatically generates the Resources class where individual resources are represented by static properties.
Add the following string resources to the Resources.resx file to be used later:
| Resource Name |
Value |
| ToolWindowTitle |
Calculate Tool Windows
|
| CanNotCreateWindow |
Cannot create tool window.
|
Step 4: Creating a ToolWindowPane
The user control representing our tool window has no idea about how to integrate with Visual Studio IDE. Window objects integrated with the IDE (a tool window is a kind of them) have a lot of features provided by the IDE itself: for example they can be docked or can float, pinned down, etc. The IDE hosts so-called window frames and window panes to provide these features. In order our custom control could work, it should be embedded within a window pane.
The key for that is to create a type representing the window pane. Our type inherits from ToolWindowPane that implements the IVsWindowPane interface responsible for the IDE integration features.
Add a new C# code file to the StartupToolset project with the name of CalculationToolWindow.cs and copy the following code into the file:
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
namespace MyCompany.StartupToolset
{
[Guid("4B1BBBA2-9D83-45a4-8899-E7CB0296D27F")]
public class CalculationToolWindow : ToolWindowPane
{
private CalculationControl control;
public CalculationToolWindow() :
base(null)
{
Caption = Resources.ToolWindowTitle;
BitmapResourceID = 300;
BitmapIndex = 0;
control = new CalculationControl();
}
override public IWin32Window Window
{
get { return control; }
}
}
}
Our tool window class is represented as a COM type in the VS IDE and so we need a GUID identifying the type, so we use the Guid attribute to decorate the class with an ID.
Our CalculationControl instance is embedded into the tool window pane through the private field control. We create an instance of this user control in the constructor of the class. The overridden Window property is to obtain the Win32 handle of the tool window represented by control.
Now let’s focus on the constructor:
public CalculationToolWindow() : base(null)
{
Caption = Resources.ToolWindowTitle;
BitmapResourceID = 300;
BitmapIndex = 0;
control = new CalculationControl();
}
The body of the constructor uses resources to set up the Caption and the bitmap of the tool window. The Caption is a string property, so we could use a string constant, but in the example I use the same pattern the VSPackage wizard does: the caption is specified in the Resources.resx file.
The bitmap is set using the BitmapResourceID and BitmapIndex property. The first must be set to the integer ID our bitmap has in the VSPackage.resx file. The IDE takes into account this bitmap as a bitmap strip, and BitmapIndex sets the index of the bitmap within the strip.
Our constructor has no parameters, but the base class has a constructor with one IServiceProvider parameter. We do not use this parameter and pass a null value. We could pass an IServiceProvider instance here to allow our window pane to obtain service instances through it.
I mentioned this because the VS 2008 SDK documentation misleadingly says: “This parameter must not be null reference (Nothing in Visual Basic), or else the tool window will not be able to add itself to the shell”. In this form this is not correct: when you run our sample you will see, our tool window can be added to the IDE.
Step 5: Let our package know its tool window
Our tool window itself is not an independent object; it is coupled with our package: this contains the logic when and how to show the tool window and it may contain the interaction logic and services.
We can associate the tool window classes with the package using the ProvideToolWindow attribute. We must provide the type of our tool window class as a mandatory parameter, in this case CalculateToolWindow:
...
[ProvideToolWindow(typeof(CalculateToolWindow))]
...
public sealed class StartupToolsetPackage : Package { ... }
A tool window can be displayed not only by the package defining it but also by other VSPackages. In a previous post (Part #5) I described the on-demand load mechanism of packages. A package should not be loaded to allow other packages be aware of the existence of a tool window. This is done through the registration mechanism just like in case of menu commands. The regpk.exe utility will use this attribute to register our tool window and associate it with our package. When any other package wants to perform an operation with our tool window, the IDE load our package (unless it is already loaded).
Step 6: Displaying the tool window
In Part #4 we treated the code to show the tool window. We use the same pattern to display the CalculateToolWindow: in the event handler method of the menu command we created in the previous post:
...
public sealed class StartupToolsetPackage : Package
{
...
private void ShowCalculateToolCallback(object sender, EventArgs e)
{
ToolWindowPane window = FindToolWindow(typeof(CalculationToolWindow), 0, true);
if ((null == window) || (null == window.Frame))
{
throw new NotSupportedException(Resources.CanNotCreateWindow);
}
IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
}
...
}
Just to remind you how it works: The key is the FindToolWindow method that searches for the CalculateToolWindow instance with ID 0. If it does not found instance, it creates a new one. The tool window is displayed using its frame’s Show method.
That’s all. Our tool window now can be displayed using the related menu command.
Tools we need
If I’d ask you what type of tools you really need when developing applications, I would get “logging” in the top 5 on your wish list. With logging it is much easier to debug and fix our app than without it. So in the remaining part of this post we are going to add simple logging to our app.
Logging VSPackages
There are many ways to log activities within an application. We can send textual messages to the console, to the Trace or Debug output, to Windows Event Log or even to Windows debug log. Visual Studio also offers other alternatives:
Visual Studio has a so-called activity log that is an XML file. We can put messages into that file and later displaying them. The activity log is useful to log important package messages.
We have an Output Window in Visual Studio, we can send messages there. We also have the option to create our own Output Window pane (like source control tools or other packages do that) in order to separate our messages from other messages.
In this part we are going to go build logging into our code. We log the event when the Calculate button is clicked in our tool window (arguments, operation and result).
What is VS Activity Log?
Visual Studio can be started in activity logging mode with the /log command line switch. In this mode VSPackage messages written to the so-called VS Activity Log are recorded into an XML file that later can be displayed and used for testing, checking, troubleshooting, etc.
When you start your Visual Studio session without the /log command, messages sent to the activity log will not be saved to the log file.
Anytime you start a new /log VS session the previous activity log file is overridden. This file is called ActivityLog.Xml and its location is within your user profile folder under the Microsoft\VisualStudio\<Hive>\UserSettings folder. The <Hive> parameter is determined by the version of Visual Studio you run (it is 9.0 for VS 2008) and the /rootsuffix command line switch that is Exp for the Experimental hive. So, when you develop packages with VS 2008 SDK, <Hive> usually has the value of 9.0Exp. Do not forget that your root user profile folder is determined by a lot of factors (your login name, profile type, OS, etc.).
For example, if your login name is jsmith and you have a roaming profile on Windows Vista, you may find the activity log under the following folder:
C:\Users\jsmith\AppData\Roaming\Microsoft\VisualStudio\9.0Exp\UserSettings
Visual Studio puts a stylesheet file (ActivityLog.xsl) in the same folder, so when you open your activity log file in IE, it will be rendered into a table by that stylesheet.
The activity log is flushed regularly, so — my experiences show — you can look into it in the middle of a VS session. Any time you close VS 2008, the stylesheet file is put into the directory. If you delete it before or in middle a VS session, you will not get it again unless VS is closed.
Using the Visual Studio activity log
You can treat the activity log as if it were a table. When you log a message it appears as a new row in the activity log table. The log contains the following columns:
| Column |
Description |
| Record ID |
Sequence number identifying the entry. The IVsActivityLog automatically creates this ID.
|
| Type |
Type of the message, one of the __ACTIVITYLOG_ENTRYTYPE enumeration values specifying the type with the corresponding name.
ALE_ERROR ALE_WARNING ALE_INFORMATION |
| Description |
This is the textual description of the event. This string can be composed by the developer.
|
| GUID |
An optional GUID value to attach to the entry. If it is set, it can be anything (e.g. a CLSID, a command ID, a package ID, etc.).
|
| Hr |
An optional HRESULT value to attach to the entry. It is generally used to record the return value of the corresponding COM call.
|
| Source |
Identifies the source of the message. This string can be the package name or anything the developer thinks identifies the source of the entry.
|
| Time |
The timestamp of the event when the message has been added to the activity log. This timestamp is created by the log, cannot be set by the developer.
|
| Path |
A file path that can be added to the entry. When displaying the activity log using the default stylesheet, this column is merged into the description.
|
If you want to use the activity log, you must obtain a reference to the IVsActivityLog interface by calling GetService with the SVsActivityLog type. This interface gives you a few methods that can be used to add messages to the activity log. These methods differ in the activity log columns they record information when adding a message. All methods require a log entry type, the source and the event text, and automatically create a Record ID for the message. For example the LogEntry method does not add any extra information while the LogEntryGuidHr adds a GUID and an Hr column to the message.
Let’s see how to add activity log messages to our code! In the following sample we are going to add a simple message using the LogEntry method. We put a simple logic into the code to set the entry type to an error, if the calculation failed. We modify the CalculationButton_Click method to add a LogCalculation method call:
private void CalculateButton_Click(object sender, EventArgs e)
{
try
{
int firstArg = Int32.Parse(FirstArgEdit.Text);
int secondArg = Int32.Parse(SecondArgEdit.Text);
int result = 0;
switch (OperatorCombo.Text)
{
case "+":
result = firstArg + secondArg;
break;
... // --- Omitted for clarity
}
ResultEdit.Text = result.ToString();
}
catch (SystemException)
{
ResultEdit.Text = "#Error";
}
LogCalculation(FirstArgEdit.Text, SecondArgEdit.Text, OperatorCombo.Text,
ResultEdit.Text);
}
Of course, there is no LogCalculation method in our class; this is the private method using the activity log:
private void LogCalculation(string firstArg, string secondArg,
string operation, string result)
{
string message = String.Format("Calculation executed: {0} {1} {2} = {3}",
firstArg, operation, secondArg, result);
IVsActivityLog log =
Package.GetGlobalService(typeof(SVsActivityLog)) as IVsActivityLog;
if (log == null) return;
log.LogEntry(
(result == "#Error")
?(UInt32) __ACTIVITYLOG_ENTRYTYPE.ALE_ERROR
: (UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION,
"Calculation", message);
}
You can see, it’s pretty easy to use the activity log. Please notice, we used the Package.GetGlobalService method to obtain a service interface instance.
Using the output window
The activity log stores messages we intend for the attention of the package developer. In many situations we want to display messages for the user of a package. The Output Window is the perfect target of them.
I think I do not have to introduce the Output Window. This is the window (generally at the bottom of our code) that shows build messages:

This window has panes (on the picture above you see the Build pane). When we write a message to the output window we actually write a message to a pane. We can use predefined output window panes or we can create our own panes. In this example we are going to use the General output window pane.
If I would ask you what do you think how we write to the output window, you would surely answer: “using a service”. It’s correct. We have the IVsOutputWindow service interface that can be obtained calling GetService with SVsOutputWindow type. This interface has only three methods: GetPane, CreatePane, DeletePane. I think the names tell everything. The real operations use the window pane returned by GetPane as an IVsOutputWindowPane instance.
So, let’s change our LogCalculation method call in CalculateButton_Click with the LogCalculationToOutput that does not change the original method signature:
private void LogCalculationToOutput(string firstArg, string secondArg,
string operation, string result)
{
string message = String.Format("Calculation executed: {0} {1} {2} = {3} ",
firstArg, operation, secondArg, result);
IVsOutputWindow outWindow =
Package.GetGlobalService(typeof (SVsOutputWindow)) as IVsOutputWindow;
Guid generalWindowGuid = VSConstants.GUID_OutWindowGeneralPane;
IVsOutputWindowPane windowPane;
outWindow.GetPane(ref generalWindowGuid, out windowPane);
windowPane.OutputString(message);
}
The key of the operation is highlighted. To be able to put a message into an output window pane, we have to obtain a reference to the pane using the GetPane method. In this example we write to the General pane that is (as all panes) identified by a GUID. The VSConstants class has a value for this pane and we use it to get a reference. The OutputString method does what we expect: puts the message to the window pane.
Running our sample application we can try a few calculations. Their corresponding messages show up in the General pane:

Where we are?
In this post we finished our example with a calculation tool window added by hand to our project. Our tool window was separated into two cooperating components: we had a user control responsible for the user interface and the simple “business” logic of calculation and a tool window pane control responsible for integrating the user control into the IDE to form a tool window. We associated the menu command created in the previous post with the “show up code” of our tool window.
We created the first simple piece of our toolset: added logging in order to follow what calculations were executed in the tool window. We used the VS activity log (this is an XML file that lives for a VS session) and the VS Output Window.
The VS Activity log is intended for the package developer (to check, debug or troubleshoot a package), while the Output Window is for the user of the package to see and follow what is happening, what has been done by the package.
In the next post we use this sample to make simple refactoring and extracting code to create new pieces of our toolset.
Posted
Jan 18 2008, 06:38 PM
by
inovak