Walkthrough: Integrating into the Properties Window, Task List, Output Window, and Options Dialog Box

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 follow this walkthrough, you must install the Visual Studio 2013 SDK. For more information, see Visual Studio Software Development Kit (SDK).

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:

  1. Under Visual Basic Extensibility. The default language of the project is Visual Basic.

  2. Under C# Extensibility. The default language of the project is C#.

  3. 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

  1. 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.

  2. 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.

  3. On the Command Options page, set Command name to Todo Manager and Command ID to cmdidTodoCommand.

  4. On the Tool Window Options page, set Window name to Todo Manager and Command ID to cmdidTodoTool.

  5. 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:

Finished Tool Window

To add controls to the tool window

  1. 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.

  2. From the All WPF Controls section of the Toolbox, drag a Canvas control to the grid.

  3. Drag a TextBox control, a Button control, and a ListBox control to the Canvas. Arrange them as in the figure above.

  4. Select the button. Set its Content property to Add.

  5. 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

  1. From the designer page, right-click View Code.

  2. 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.

  3. Save your work.

  4. Now, add a parameter to the code that calls the constructor.

    In Solution Explorer, open MyToolWindow.cs or MyToolWindow.vb.

  5. Find the line in the MyToolWindow constructor that resembles the following code.

    Me.Content = New MyControl()
    
    base.Content = new MyControl();
    
  6. 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

  1. 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));
    }
    
  2. 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

  1. In Solution Explorer, right-click the ToDoList project, point to Add, and then click Class.

  2. 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;
    
  3. Modify the ToolsOptions class declaration to inherit from DialogPage.

    Inherits DialogPage
    
    class ToolsOptions : DialogPage
    
  4. 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.

  5. 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

  1. In Solutions Explorer, open TodoListPackage.cs or TodoListPackage.vb.

  2. 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.

  3. 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.

    Options Page

    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

  1. In Solution Explorer, right-click the ToDoList project, point to Add, and then click Class.

  2. 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.

    Properties Window

  3. 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;
    
  4. 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.

  5. 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.

  6. 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();
    }
    
  7. Open MyControl.xaml.cs or MyControl.xaml.vb.

  8. 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

  1. Switch to the design view of MyControl.xaml, then double-click the Button control

  2. 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.

  3. Switch to the design view of MyControl.xaml to add the code that handles user selection of a new item in the ListBox.

  4. 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.

  5. 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()
    {
    }
    
  6. Save your work. You can build your project and look for typos.

  7. 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.

  8. 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;
    
  9. 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);
        }
    }        
    
  10. 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.

  11. 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);
    }
    
  12. 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.

  13. 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.

  14. 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.

  15. 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

  1. Open MyControl.xaml.cs or MyControl.xaml.vb.

  2. 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.

  3. 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.

  4. 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

  1. Press CTRL+F5 to open the experimental build of Visual Studio.

  2. In the experimental build, on the Tools menu, click ToDo Manager.

    The tool window that you designed should open.

  3. Type something in the TextBox and then click Add.

    You should see that the item is added to the ListBox.

  4. 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.

  5. 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.

  6. Click one of the items in the ListBox.

    The Properties window displays the two properties for the item.

  7. 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)

Task List

Other Resources

Commands, Menus, and Toolbars

Tool Windows

Properties Window and Property Pages