Walkthrough: Integrating into the Properties Window, Task List, Output Window, and Options Dialog Box (Part 4 of 4)
By using the Visual Studio SDK, you can enable your code to access any tool window in Visual Studio. For example, you can add entries to the Task List, add text to the Output window, or integrate your extension into the Properties window so that users can configure the extension by setting properties. This walkthrough shows how to integrate your extension into tool windows in Visual Studio.
By completing this walkthrough, you can learn how to do the following things:
Create a VSPackage by using the package template.
Implement the generated tool window.
Implement a menu command handler.
Create an Options page.
Make data available to the Properties window.
Integrate into the Properties window.
Add text to the Output window and items to Task List.
This walkthrough is part of a series that teaches how to extend the Visual Studio IDE. For more information, see Walkthroughs for Customizing Visual Studio By Using VSPackages.
Prerequisites
To complete this walkthrough, you must install the Visual Studio 2010 SDK.
Note
For more information about the Visual Studio SDK, see Extending Visual Studio Overview. To find out how to download the Visual Studio SDK, see Visual Studio Extensibility Developer Center on the MSDN Web site.
Locations for the Visual Studio Package Project Template
The Visual Studio Package project template can be found in three different locations in the New Project dialog:
Under Visual Basic Extensibility. The default language of the project is Visual Basic.
Under C# Extensibility. The default language of the project is C#.
Under Other Project Types Extensibility. The default language of the project is C++.
Create a VSPackage By Using the Visual Studio Package Template
To create a VSPackage
Create a VSPackage. For more information about how to create a VSPackage, see Walkthrough: Creating a Menu Command By Using the Visual Studio Package Template.
Name the project TodoList, set the language to Visual C# or Visual Basic, and on the Select VSPackage Options page, select both Menu Command and Tool Window.
On the Command Options page, set Command name to Todo Manager and Command ID to cmdidTodoCommand.
On the Tool Window Options page, set Window name to Todo Manager and Command ID to cmdidTodoTool.
Click the Finish button.
Implement the Generated Tool Window
The package template generated a basic tool window in the form of a user control. However, it has no functionality. To give it functionality, you must add child controls and modify the code in MyControl.xaml.cs or MyControl.vb.
The tool window will include a TextBox in which to type a new ToDo item, a Button to add the new item to the list, and a ListBox to display the items on the list. The completed tool window should resemble the following picture:
To add controls to the tool window
Delete the button, text, and StackPanel controls from the grid.
Note
This does not delete the button1_Click event handler, which you will reuse in a later step.
From the All WPF Controls section of the Toolbox, drag a Canvas control to the grid.
Drag a TextBox control, a Button control, and a ListBox control to the Canvas. Arrange them as in the figure above.
Select the button. Set its Content property to Add.
In the XAML pane, reconnect the button event handler to the Button control by adding a Click="button1_Click" attribute. The resulting line of XAML should look like this:
Public _parent As MyToolWindow Public Sub New(ByVal parent As MyToolWindow) InitializeComponent() _parent = parent End Sub
<Button Content="Add" Height="21" Name="button1" Width="50" Canvas.Left="345" Canvas.Top="6" Click="button1_Click" />
Save your work.
By default, the user control constructor in the MyControl.xaml.cs or MyControl.xaml.vb file takes no parameters. However, you can customize the constructor to include parameters so that you can save the parent for later use.
To customize the constructor
From the designer page, right-click View Code.
Replace the existing constructor with the following code:
public MyToolWindow _parent; public MyControl(MyToolWindow parent) { InitializeComponent(); _parent = parent; }
Doing this enables the constructor to take a parameter of type MyToolWindow.
Save your work.
Now, add a parameter to the code that calls the constructor.
In Solution Explorer, open MyToolWindow.cs or MyToolWindow.vb.
Find the line in the MyToolWindow constructor that resembles the following code.
Me.Content = New MyControl()
base.Content = new MyControl();
Change the line as follows.
Me.Content = New MyControl(Me)
base.Content = new MyControl(this);
Doing this passes the instance of the tool window to the user control. (This is required in a later step to create the constructor for the ToDoItem class.)
Implement a Menu Command Handler
When the TodoList project was created, it included a default handler for the menu item. The handler is in the TodoListPackage file. Now, add code to the handler to display the tool window. You can do this in just a couple of steps because TodoListPackage already contains a function named ShowToolWindow.
To implement the menu item handler
Open TodoListPackage.cs or TodoListPackage.vb. Notice that the menu item handler contains the following sample code.
Private Sub MenuItemCallback(ByVal sender As Object, ByVal e As EventArgs) ' Show a Message Box to prove we were here Dim uiShell As IVsUIShell = TryCast(GetService(GetType(SVsUIShell)), IVsUIShell) Dim clsid As Guid = Guid.Empty Dim result As Integer Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox(0, clsid, "TodoList", String.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", Me.GetType().Name), String.Empty, 0, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_INFO, 0, result)) End Sub
private void MenuItemCallback(object sender, EventArgs e) { // Show a Message Box to prove we were here IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell)); Guid clsid = Guid.Empty; int result; Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox( 0, ref clsid, "TodoList", string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.ToString()), string.Empty, 0, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_INFO, 0, // false out result)); }
Remove everything in the function, and replace it with a call to ShowToolWindow as follows.
Private Sub MenuItemCallback(ByVal sender As Object, ByVal e As EventArgs) ShowToolWindow(sender, e) End Sub
private void MenuItemCallback(object sender, EventArgs e) { ShowToolWindow(sender, e); }
Save your work, and then press F5 to build the project and open it in the experimental build of Visual Studio. Test whether the tool window opens by clicking ToDo Manager on the Tools menu.
Close the experimental build before you continue.
Create an Options Page
You can provide a page in the Options dialog box so that users can change settings for the tool window. Creating an Options page requires both a class that describes the options and an entry in the TodoListPackage.cs or TodoListPackage.vb file.
To create an Options page
In Solution Explorer, right-click the ToDoList project, point to Add, and then click Class.
In the Add New Item dialog box, name the file ToolsOptions.cs, or ToolsOptions.vb and then click Add.
Visual Studio creates a class named ToolsOptions in this file, but you must modify the class header so that the class is derived from DialogPage.
Add the Microsoft.VisualStudio.Shell namespace to the existing using/imports directives, as follows.
Imports Microsoft.VisualStudio.Shell
using Microsoft.VisualStudio.Shell;
Modify the ToolsOptions class declaration to inherit from DialogPage.
Inherits DialogPage
class ToolsOptions : DialogPage
The Options page in this walkthrough will only provide one option named DaysAhead. To add this option, add a property named DaysAhead to the ToolsOptions class as follows.
Private _daysAhead As Double Public Property DaysAhead() As Double Get Return _daysAhead End Get Set(ByVal value As Double) _daysAhead = value End Set End Property
private double _daysAhead; public double DaysAhead { get { return _daysAhead; } set { _daysAhead = value; } }
This class stores a single option as a private member named _daysAhead. The class then provides a public property named DaysAhead for accessing the option.
Save the file.
Now you must make the project aware of this Options page so that it will be correctly registered and available to users.
To make the Options page available to users
In Solutions Explorer, open TodoListPackage.cs or TodoListPackage.vb.
Find the line that contains the ProvideToolWindowAttribute attribute, and then add a ProvideOptionPageAttribute attribute immediately after it, as follows.
<PackageRegistration(UseManagedResourcesOnly:=True), _ InstalledProductRegistration("#110", "#112", "1.0", IconResourceID:=400), _ ProvideMenuResource("Menus.ctmenu", 1), _ ProvideToolWindow(GetType(MyToolWindow)), _ ProvideOptionPage(GetType(ToolsOptions), "To-Do", "General", 101, 106, True), _ Guid(GuidList.guidTodoListPkgString)> _ Public NotInheritable Class TodoListPackage Inherits Package
[ProvideToolWindow(typeof(MyToolWindow))] [ProvideOptionPage(typeof(ToolsOptions), "To-Do", "General", 101, 106, true)]
Note
You do not have to include the word 'Attribute' in attribute declarations.
Save the file.
The first parameter to the ProvideOptionPage constructor is the type of the class ToolsOptions, which you created earlier. The second parameter, "To-Do", is the name of the category in the Options dialog box. The third parameter, "General", is the name of the subcategory of the Options dialog box where the Options page will be available. The next two parameters are resource IDs for strings; the first is the name of the category, and the second is the name of the subcategory. The final parameter sets whether this page can be accessed by using Automation.
When your Options page is accessed, it should resemble the following picture.
Notice the category To-Do and the subcategory General.
Make Data Available to the Properties Window
By following principles for good object-oriented design, you can make a class named ToDoItem that stores information about the individual items in the To-Do list.
To make data available in the Properties window
In Solution Explorer, right-click the ToDoList project, point to Add, and then click Class.
In the Add New Item dialog box, name the file ToDoItem.cs or ToDoItem.vb, and then click Add.
When the tool window is available to users, the items in the ListBox will be represented by ToDoItem instances. When the user selects one of these items in the ListBox, the Properties window will display information about the item.
To make data available in the Properties window, make the data into public properties of a class and then document them by using two special attributes, Description and Category. Description is the text that appears at the bottom of the Properties window. Category defines where the property should appear when the Properties window is displayed in Categorized view. In the following picture, the Properties window is in Categorized view, the Name property in the To-Do Fields category is selected, and the description of the Name property is displayed at the bottom of the window.
Add the following namespaces to the top of the ToDoItem.cs or ToDoItem.vb file, after the existing using/imports statements.
Imports System.ComponentModel Imports System.Windows.Forms Imports Microsoft.VisualStudio.Shell.Interop
using System.ComponentModel; using System.Windows.Forms; using Microsoft.VisualStudio.Shell.Interop;
Begin implementing the ToDoItem class as follows. Make sure to add the public access modifier to the class declaration.
Private _name As String <Description("Name of the To-Do item")> _ <Category("To-Do Fields")> _ Public Property Name() As String Get Return _name End Get Set(ByVal value As String) _name = value _parent.UpdateList(Me) End Set End Property Private _dueDate As Date <Description("Due date of the To-Do item")> _ <Category("To-Do Fields")> _ Public Property DueDate() As Date Get Return _dueDate End Get Set(ByVal value As Date) _dueDate = value _parent.UpdateList(Me) _parent.CheckForErrors() End Set End Property
public class ToDoItem { private string _name; [Description("Name of the To-Do item")] [Category("To-Do Fields")] public string Name { get { return _name; } set { _name = value; _parent.UpdateList(this); } } private DateTime _dueDate; [Description("Due date of the To-Do item")] [Category("To-Do Fields")] public DateTime DueDate { get { return _dueDate; } set { _dueDate = value; _parent.UpdateList(this); _parent.CheckForErrors(); } } }
Notice that this code has two properties, Name and DueDate. These are the two properties that will appear in the Properties window, as shown in the previous picture. Each property is preceded by the Description and Category attributes, which provide the information for display in the Properties window. Examine these two attributes for the Name property; the strings should match the ones in the picture.
Add the following constructor function at the top of the class.
Private _parent As MyControl Public Sub New(ByVal parent As MyControl, ByVal name As String) _parent = parent _name = name _dueDate = Date.Now Dim daysAhead As Double = 0 Dim package As IVsPackage = TryCast(_parent._parent.Package, IVsPackage) If package IsNot Nothing Then Dim obj As Object package.GetAutomationObject("To-Do.General", obj) Dim options As ToolsOptions = TryCast(obj, ToolsOptions) If options IsNot Nothing Then daysAhead = options.DaysAhead End If End If _dueDate = _dueDate.AddDays(daysAhead) End Sub
private MyControl _parent; public ToDoItem(MyControl parent, string name) { _parent = parent; _name = name; _dueDate = DateTime.Now; double daysAhead = 0; IVsPackage package = _parent._parent.Package as IVsPackage; if (package != null) { object obj; package.GetAutomationObject("To-Do.General", out obj); ToolsOptions options = obj as ToolsOptions; if (options != null) { daysAhead = options.DaysAhead; } } _dueDate = _dueDate.AddDays(daysAhead); }
First, this code declares a private member named _parent, which corresponds to the user control that contains the TextBox, Button, and ListBox controls that you created earlier. The constructor takes the user control as a parameter, together with a string that is the name for this ToDo item. The first three lines in the constructor save the user control, the name, and the current date and time.
You can use the current date and time as a basis for enabling the DaysAhead option on the Option page that you created earlier. Because the current date and time are not typically used as a due date, you can advance the current date by the number of days that are specified on the Options page. .
The code declares a local variable called daysAhead that is set by using the value in the DaysAhead option. The next line obtains the parent of the user control, and from there, the package member. (This is where you use the _parent member that you added to the MyControl.xaml.cs class earlier.)
If this package member is not null, an object is declared that will hold the ToolsOptions instance. To get the instance, the code calls the GetAutomationObject member of the package and passes the name of the category and subcategory as a single dot-delimited string, To-Do.General. The results are passed as an output parameter back into the obj variable.
The obj variable is then cast to the ToolsOptions class and saved in a variable named options. If this variable is not null, the code obtains the DaysAhead member and saves it into the _daysAhead variable.
The code then advances the _duedate variable by the number of days ahead by using the AddDays method.
Because instances of the ToDoItem class will be stored in the ListBox and the ListBox will call the ToString function that this class inherits from the base Object class to retrieve the string to display for the item, you must overload the ToString function.
Add the following code to ToDoItem.cs, after the constructor and before the end of the class.
Public Overloads Overrides Function ToString() As String Return (_name & " Due: ") + _dueDate.ToShortDateString() End Function
public override string ToString() { return _name + " Due: " + _dueDate.ToShortDateString(); }
Open MyControl.xaml.cs or MyControl.xaml.vb.
Add stub methods to the MyControl class for the CheckForError and UpdateList methods. Put them after the ProcessDialogChar and before the end of the file.
Public Sub CheckForErrors() End Sub Public Sub UpdateList(ByVal item As ToDoItem) End Sub Public Sub CheckForErrors() End Sub Public Sub UpdateList(ByVal item As ToDoItem) End Sub
public void CheckForErrors() { } public void UpdateList(ToDoItem item) { }
The CheckForError method will call a method that has the same name in the parent object, and that method will check whether any errors have occurred and handle them correctly. The UpdateList method will update the ListBox in the parent control; the method is called when the Name and DueDate properties in this class change. You will implement these methods in a later step.
Integrate into the Properties Window
Now write the code that manages the ListBox, which will be tied to the Properties window.
You must add a handle to the button that reads the TextBox, creates a ToDoItem instance, and adds the instance to the ListBox.
To integrate with the Properties window
Switch to the design view of MyControl.xaml, then double-click the Button control
Replace the existing button1_Click handler function by using the following code.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click If TextBox1.Text.Length > 0 Then Dim item = New ToDoItem(Me, TextBox1.Text) ListBox1.Items.Add(item) TrackSelection() CheckForErrors() End If End Sub
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions")] private void button1_Click(object sender, EventArgs e) { if (textBox1.Text.Length > 0) { var item = new ToDoItem(this, textBox1.Text); listBox1.Items.Add(item); TrackSelection(); CheckForErrors(); } }
This code creates a new ToDoItem instance and passes the user control instance as a parameter together with the text that the user entered in the TextBox control. Next, the code adds the item to the ListBox. (The ListBox will call the ToString method of the ToDoItem instance to retrieve the string to display in the ListBox.) Next, the code calls the TrackSelection function, which you will write in a later step. Finally, the code checks for errors.
Switch to the design view of MyControl.xaml to add the code that handles user selection of a new item in the ListBox.
Click the ListBox control. In the Properties window, double-click the SelectionChanged event. Doing this adds a stub for a SelectionChanged handler and assigns it to the event.
Fill in the SelectionChanged handler as follows, and stub in the method it calls.
Private Sub ListBox1_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) Handles ListBox1.SelectionChanged TrackSelection() End Sub Private Sub TrackSelection() End Sub
private void listBox1_SelectionChanged(object sender, EventArgs e) { TrackSelection(); } private void TrackSelection() { }
Save your work. You can build your project and look for typos.
Now, fill in the TrackSelection function, which will provide integration with the Properties window. This function is called when the user adds an item to the ListBox or clicks an item in the ListBox.
Now that you have a class that the Properties window can use, you can integrate the Properties window with the tool window. When the user clicks an item in the ListBox in the tool window, the Properties window should be updated accordingly. Similarly, when the user changes a ToDo item in the Properties window, the associated item should be updated.
Note
As an alternative, you can generate PropertyChanged events directly by implementing the INotifyPropertyChanged interface.
Put the code for updating the Properties window in the TrackSelection function. Doing this will tie the ToDoItem object to the Properties window; you do not have to write any additional code to modify the ToDoItem when the user changes a value in the Properties window. The Properties window will automatically call the set property accessors to update the values. However, you must finish the UpdateList method that you created when you wrote the code for the ToDoItem class.
Add the following namespace declarations to the top of the MyControl.xaml.cs or MyControl.vb file, after the existing using/imports statements.
Imports System Imports System.Runtime.InteropServices Imports Microsoft.VisualStudio.Shell.Interop Imports Microsoft.VisualStudio Imports Microsoft.VisualStudio.Shell
using System.Runtime.InteropServices; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell;
Implement the TrackSelection function as follows.
Private mySelContainer As SelectionContainer Private mySelItems As System.Collections.ArrayList Private frame As IVsWindowFrame = Nothing Private Sub TrackSelection() If frame Is Nothing Then Dim shell = TryCast(GetService(GetType(SVsUIShell)), IVsUIShell) If shell IsNot Nothing Then Dim guidPropertyBrowser = New Guid(ToolWindowGuids.PropertyBrowser) shell.FindToolWindow(CUInt(__VSFINDTOOLWIN.FTW_fForceCreate), guidPropertyBrowser, frame) End If End If If frame IsNot Nothing Then frame.Show() End If If mySelContainer Is Nothing Then mySelContainer = New SelectionContainer() End If mySelItems = New System.Collections.ArrayList() Dim selected = TryCast(listBox1.SelectedItem, ToDoItem) If selected IsNot Nothing Then mySelItems.Add(selected) End If mySelContainer.SelectedObjects = mySelItems Dim track = TryCast(GetService(GetType(STrackSelection)), ITrackSelection) If track IsNot Nothing Then track.OnSelectChange(mySelContainer) End If End Sub
private SelectionContainer mySelContainer; private System.Collections.ArrayList mySelItems; private IVsWindowFrame frame = null; private void TrackSelection() { if (frame == null) { var shell = GetService(typeof(SVsUIShell)) as IVsUIShell; if (shell != null) { var guidPropertyBrowser = new Guid(ToolWindowGuids.PropertyBrowser); shell.FindToolWindow((uint)__VSFINDTOOLWIN.FTW_fForceCreate, ref guidPropertyBrowser, out frame); } } if (frame != null) { frame.Show(); } if (mySelContainer == null) { mySelContainer = new SelectionContainer(); } mySelItems = new System.Collections.ArrayList(); var selected = listBox1.SelectedItem as ToDoItem; if (selected != null) { mySelItems.Add(selected); } mySelContainer.SelectedObjects = mySelItems; var track = GetService(typeof(STrackSelection)) as ITrackSelection; if (track != null) { track.OnSelectChange(mySelContainer); } }
Add the following code just after the end of the TrackSelection function.
Protected Function GetService(ByVal service As Type) As Object Dim obj As Object = Nothing If _parent IsNot Nothing Then obj = _parent.GetVsService(service) End If Return obj End Function
protected object GetService(Type service) { if (_parent != null) { return _parent.GetVsService(service); } return null; }
This code calls the GetService function. This function first tries to obtain the service from the parent tool window by calling its GetService function. If that fails, it tries to obtain it from the GetService function of the object. Because the GetService function in the parent tool window is not public, the code calls GetVsService instead. You must add the GetVsService function.
Open MyToolWindow.cs or MyToolWindow.vb. Add the following code to the end of the class, just before the end of the file.
Friend Function GetVsService(ByVal service As Type) As Object Return GetService(service) End Function
internal object GetVsService(Type service) { return GetService(service); }
Save the file.
The first time the TrackSelection function runs, it calls GetService to obtain an instance of the Visual Studio shell. It then uses that instance to obtain an object for the Properties window. To get the Properties window object, the code starts by using the GUID that represents the Properties window. (The GUIDs for the tool windows are members of the ToolWindowGuids80 class.) The code then calls the FindToolWindow function of the shell, by passing the GUID, to get the Properties window object. Doing this saves it in the frame variable so that when the function is called again, this process of obtaining the Properties window does not have to be repeated.
Next, the method calls the Show method of the frame variable to display the Properties window.
The next code gathers the selected items in the ListBox. The ListBox is not configured to enable multiple selection. To pass the selected item to the Properties window, you must use a container. Therefore, the code gathers the selected item and puts it in an ArrayList, and then puts that ArrayList in a container of type SelectionContainer.
Next, the code calls GetService to obtain an instance of ITrackSelection, which is the Visual Studio object that tracks selected objects in the user interface (UI) and displays their properties. Then the code directly calls the ITrackSelection OnSelectChange event handler, and passes the SelectionContainer that is holding the selected item. The result is that the Properties window displays the properties for the selected item.
When the user changes a ToDoItem object in the Properties window, the Properties window automatically calls the set accessor functions in the ToDoItem object. That updates the object, but you still have to update the ListBox.
In an earlier step, you added code in the set accessor functions to call an UpdateList function in MyControl.xaml.cs or MyControl.xaml.vb. Now, add the rest of the UpdateList function code.
From MyControl.xaml.cs or MyControl.xaml.vb, implement the UpdateList method as follows.
Public Sub UpdateList(ByVal item As ToDoItem) Dim index As Integer = ListBox1.SelectedIndex listBox1.Items.RemoveAt(index) listBox1.Items.Insert(index, item) ListBox1.SelectedItem = index End Sub
public void UpdateList(ToDoItem item) { var index = listBox1.SelectedIndex; listBox1.Items.RemoveAt(index); listBox1.Items.Insert(index, item); listBox1.SelectedItem = index; }
This code determines which item is selected and will correspond to the ToDoItem that is being modified. The code removes the item from the ListBox, and then re-inserts it. Doing this updates the line in the ListBox for the item. Then the code sets the selection back to the same item.
Save your work.
Add Text to the Output Window and Items to the Task List
To add strings to the Task List and Output window, you must first obtain objects that refer to those two windows. Then, you can call methods on the objects. For the Task List, you create a new object of type Task, and then add that Task object to the Task List by calling its Add method. To write to the Output window, you call its GetPane method to obtain a pane object, and then you call the OutputString method of the pane object.
To add text to the Output window and the Task List
Open MyControl.xaml.cs or MyControl.xaml.vb.
Expand the button1_Click method by inserting the following code before the call to TrackSelection().
Dim outputWindow = TryCast(GetService(GetType(SVsOutputWindow)), IVsOutputWindow) Dim pane As IVsOutputWindowPane Dim guidGeneralPane As Guid = VSConstants.GUID_OutWindowGeneralPane outputWindow.GetPane(guidGeneralPane, pane) If pane IsNot Nothing Then pane.OutputString(String.Format("To Do item created: {0} \r\n", item.ToString())) End If
private void button1_Click(object sender, EventArgs e) { if (textBox1.Text.Length > 0) { var item = new ToDoItem(this, textBox1.Text); listBox1.Items.Add(item); //Insert this section------------------ var outputWindow = GetService( typeof(SVsOutputWindow)) as IVsOutputWindow; IVsOutputWindowPane pane; Guid guidGeneralPane = VSConstants.GUID_OutWindowGeneralPane; outputWindow.GetPane(ref guidGeneralPane, out pane); if (pane != null) { pane.OutputString(string.Format( "To Do item created: {0}\r\n", item.ToString())); } //------------------------------------- TrackSelection(); CheckForErrors(); }
This code obtains the object for the Output window. The object exposes an IVsOutputWindow interface. The code then obtains an IVsOutputWindowPane object that includes the OutputString function, which ultimately writes to the Output window.
Now implement the CheckForErrors method, as follows.
Public Sub CheckForErrors() For Each item As ToDoItem In ListBox1.Items If item.DueDate < DateTime.Now Then ReportError("To Do Item is out of date: " & item.ToString()) End If Next End Sub
public void CheckForErrors() { foreach (ToDoItem item in listBox1.Items) { if (item.DueDate < DateTime.Now) { ReportError("To Do Item is out of date: " + item.ToString()); } } }
This code calls the ReportError method, which you will create next, together with some other methods that help to add items to the Task List.
Add the following code to the end of the class, just before the two closing braces.
<Guid("72de1eAD-a00c-4f57-bff7-57edb162d0be")> _ Public Class MyTaskProvider Inherits TaskProvider Public Sub New(ByVal sp As IServiceProvider) MyBase.New(sp) End Sub End Class Private _taskProvider As MyTaskProvider Private Sub CreateProvider() If _taskProvider Is Nothing Then _taskProvider = New MyTaskProvider(_parent) _taskProvider.ProviderName = "To Do" End If End Sub Private Sub ClearError() CreateProvider() _taskProvider.Tasks.Clear() End Sub Private Sub ReportError(ByVal p As String) CreateProvider() Dim errorTask = New Task() errorTask.CanDelete = False errorTask.Category = TaskCategory.Misc errorTask.Text = p _taskProvider.Tasks.Add(errorTask) _taskProvider.Show() Dim taskList = TryCast(GetService(GetType(SVsTaskList)), IVsTaskList2) If taskList Is Nothing Then Exit Sub End If Dim guidProvider = GetType(MyTaskProvider).GUID taskList.SetActiveProvider(guidProvider) End Sub
[Guid("72de1eAD-a00c-4f57-bff7-57edb162d0be")] public class MyTaskProvider : TaskProvider { public MyTaskProvider(IServiceProvider sp) : base(sp) { } } private MyTaskProvider _taskProvider; private void CreateProvider() { if (_taskProvider == null) { _taskProvider = new MyTaskProvider(_parent); _taskProvider.ProviderName = "To Do"; } } private void ClearError() { CreateProvider(); _taskProvider.Tasks.Clear(); } private void ReportError(string p) { CreateProvider(); var errorTask = new Task(); errorTask.CanDelete = false; errorTask.Category = TaskCategory.Misc; errorTask.Text = p; _taskProvider.Tasks.Add(errorTask); _taskProvider.Show(); var taskList = GetService(typeof(SVsTaskList)) as IVsTaskList2; if (taskList == null) { return; } var guidProvider = typeof(MyTaskProvider).GUID; taskList.SetActiveProvider(ref guidProvider); }
At the start of this code is a specialized TaskProvider class named MyTaskProvider that includes a GUID. Next is a member variable of this new class type, followed by a method that creates the new instance when it is required.
Next come two important methods, ClearError, which clears out the existing task items, and ReportError, which adds items to the Task List.
The ReportError method creates a new instance of Task, initializes the instance, and then adds the instance to the Task List. The new Task List entries are only visible when the user selects the ToDo item in the drop-down list at the top of the Task List. The final two lines in the code automatically select the ToDo item from the drop-down list and bring the new task items into view. The GUID is required when the TaskProvider class is inherited because the SetActiveProvider method requires a GUID as a parameter.
Trying It Out
To test the extension
Press CTRL+F5 to open the experimental build of Visual Studio.
In the experimental build, on the Tools menu, click ToDo Manager.
The tool window that you designed should open.
Type something in the TextBox and then click Add.
You should see that the item is added to the ListBox.
Type something else and then click Add again.
As you add items, the initial date is set to the current date and time. This triggers an error and also an entry in the Task List.
On the View menu, click Output to open the Output window.
Notice that every time that you add an item, a message is displayed in the Task List pane.
Click one of the items in the ListBox.
The Properties window displays the two properties for the item.
Change one of the properties and then press ENTER.
The item is updated in the ListBox.
What's Next
In this walkthrough you created a tool window that is integrated with another tool window in Visual Studio. Visual Studio has several tool windows that you can work with, and the GUIDs for these can be found in the ToolWindowGuids class. You also created a class that contains properties that the Properties window can access. You provided accessor functions that the Properties window uses. In the set accessor function, you called into your own code to handle changes that were made in the Properties window. Doing this provides a two-way communication mechanism. Finally, you learned how to add items to the Task List, how to bring the items into view, and how to add text to the Output window.
See Also
Concepts
Output Window (Visual Studio SDK)