扩展属性、任务列表、输出和选项窗口

可以在 Visual Studio 中访问任何工具窗口。 本演练演示如何将有关工具窗口的信息集成到新的“选项”页和“属性”页上的新设置中,以及如何写入“任务列表”和“输出”窗口。

使用工具窗口创建扩展

  1. 使用 VSIX 模板创建名为 TodoList 的项目,并添加名为 TodoWindow 的自定义工具窗口项模板。

    注意

    有关使用工具窗口创建扩展的详细信息,请参阅 使用工具窗口创建扩展。

设置工具窗口

添加用于键入新 ToDo 项的 TextBox、向列表中添加新项的按钮,以及用于显示列表上的项的 ListBox。

  1. TodoWindow.xaml 中,从 UserControl 中删除 Button、TextBox 和 StackPanel 控件。

    注意

    这不会删除 button1_Click 事件处理程序,将在后面的步骤中重复使用该事件处理程序。

  2. 从工具箱“所有 WPF 控件”部分,将画布控件拖到网格。

  3. TextBox按钮ListBox 拖到画布上。 排列元素,使 TextBox 和 Button 位于同一级别,ListBox 将填充其下方的其余窗口,如下图所示。

    Finished Tool Window

  4. 在 XAML 窗格中,找到按钮并将其内容属性设置为 “添加”。 通过添加 Click="button1_Click" 属性将按钮事件处理程序重新连接到 Button 控件。 Canvas 块应如下所示:

    <Canvas HorizontalAlignment="Left" Width="306">
        <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="208"/>
            <Button x:Name="button" Content="Add" HorizontalAlignment="Left" Margin="236,13,0,0" VerticalAlignment="Top" Width="48" Click="button1_Click"/>
            <ListBox x:Name="listBox" HorizontalAlignment="Left" Height="222" Margin="10,56,0,0" VerticalAlignment="Top" Width="274"/>
    </Canvas>
    

自定义构造函数

  1. TodoWindowControl.xaml.cs 文件中,添加以下 using 指令:

    using System;
    
  2. 添加对 TodoWindow 的公共引用,并让 TodoWindowControl 构造函数采用 TodoWindow 参数。 代码应如下所示:

    public TodoWindow parent;
    
    public TodoWindowControl(TodoWindow window)
    {
        InitializeComponent();
        parent = window;
    }
    
  3. TodoWindow.cs 中,将 TodoWindowControl 构造函数更改为包含 TodoWindow 参数。 代码应如下所示:

    public TodoWindow() : base(null)
    {
        this.Caption = "TodoWindow";
        this.BitmapResourceID = 301;
        this.BitmapIndex = 1;
    
         this.Content = new TodoWindowControl(this);
    }
    

“创建选项”页

可以在“选项”对话框中提供页面,以便用户可以更改工具窗口的设置。 创建“选项”页需要描述 TodoListPackage.csTodoListPackage.vb 文件中的选项和条目的类。

  1. 添加名为的 ToolsOptions.cs 的类。 ToolsOptions使类继承自 DialogPage.

    class ToolsOptions : DialogPage
    {
    }
    
  2. 添加以下 using 指令:

    using Microsoft.VisualStudio.Shell;
    
  3. 本演练中的“选项”页仅提供一个名为 DaysAhead 的选项。 将名为 daysAhead 的私有字段和名为 DaysAhead 的属性添加到 ToolsOptions 类:

    private double daysAhead;
    
    public double DaysAhead
    {
        get { return daysAhead; }
        set { daysAhead = value; }
    }
    

    现在,必须让项目了解此“选项”页。

使“选项”页可供用户使用

  1. TodoWindowPackage.cs 中,向类添加 a ProvideOptionPageAttribute TodoWindowPackage

    [ProvideOptionPage(typeof(ToolsOptions), "ToDo", "General", 101, 106, true)]
    
  2. ProvideOptionPage 构造函数的第一个参数是前面创建的类 ToolsOptions的类型。 第二个参数“ToDo”是“选项”对话框中类别的名称。 第三个参数“常规”是“选项”对话框的子类别的名称,其中“选项”页将可用。 接下来的两个参数是字符串的资源 ID;第一个是类别的名称,第二个是子类别的名称。 最后一个参数确定是否可以使用自动化访问此页面。

    当用户打开“选项”页面时,它应如下图所示。

    Options Page

    请注意类别 ToDo 和子类别 常规

使数据可供属性窗口使用

可以通过创建一个名为将 TodoItem 有关单个项的信息存储在 ToDo 列表中的类来提供 ToDo 列表信息。

  1. 添加名为的 TodoItem.cs 的类。

    当工具窗口可供用户使用时,ListBox 中的项将由 TodoItems 表示。 当用户在 ListBox 中选择其中一个项目时,“ 属性” 窗口将显示有关该项的信息。

    若要在 “属性” 窗口中提供数据,可将数据转换为具有两个特殊属性的公共属性, Description 以及 CategoryDescription是显示在“属性”窗口底部的文本。 Category确定当“属性”窗口显示在“分类”视图中时应显示该属性的位置。 在下图中,“属性”窗口位于“分类”视图中,选择了“ToDo 字段”类别中的“名称”属性,并在窗口底部显示 Name 属性的说明

    Properties Window

  2. 添加以下 using 指令 TodoItem.cs 文件。

    using System.ComponentModel;
    using System.Windows.Forms;
    using Microsoft.VisualStudio.Shell.Interop;
    
  3. public 访问修饰符添加到类声明。

    public class TodoItem
    {
    }
    

    添加两个属性, Name 以及 DueDate。 我们将执行后续CheckForErrors()操作UpdateList()

    public class TodoItem
    {
        private TodoWindowControl parent;
        private string name;
        [Description("Name of the ToDo item")]
        [Category("ToDo Fields")]
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                parent.UpdateList(this);
            }
        }
    
        private DateTime dueDate;
        [Description("Due date of the ToDo item")]
        [Category("ToDo Fields")]
        public DateTime DueDate
        {
            get { return dueDate; }
            set
            {
                dueDate = value;
                parent.UpdateList(this);
                parent.CheckForErrors();
            }
        }
    }
    
  4. 向用户控件添加专用引用。 添加一个构造函数,该构造函数采用用户控件和此 ToDo 项的名称。 若要查找其 daysAhead值,它将获取“选项”页属性。

    private TodoWindowControl parent;
    
    public TodoItem(TodoWindowControl control, string itemName)
    {
        parent = control;
        name = itemName;
        dueDate = DateTime.Now;
    
        double daysAhead = 0;
        IVsPackage package = parent.parent.Package as IVsPackage;
        if (package != null)
        {
            object obj;
            package.GetAutomationObject("ToDo.General", out obj);
    
            ToolsOptions options = obj as ToolsOptions;
            if (options != null)
            {
                daysAhead = options.DaysAhead;
            }
        }
    
        dueDate = dueDate.AddDays(daysAhead);
    }
    
  5. 由于类的 TodoItem 实例将存储在 ListBox 中,ListBox 将调用该 ToString 函数,因此必须重载该 ToString 函数。 将以下代码添加到 TodoItem.cs,在构造函数之后和类末尾之前。

    public override string ToString()
    {
        return name + " Due: " + dueDate.ToShortDateString();
    }
    
  6. TodoWindowControl.xaml.cs 中,向类CheckForErrorUpdateList中添加存根方法和TodoWindowControl方法。 将它们放在 ProcessDialogChar 和文件末尾之前。

    public void CheckForErrors()
    {
    }
    public void UpdateList(TodoItem item)
    {
    }
    

    该方法CheckForError将调用父对象中同名的方法,该方法将检查是否发生了任何错误并正确处理它们。 该方法UpdateList将更新父控件中的 ListBox;此方法在此类中的属性DueDate更改时Name调用。 稍后将实施它们。

集成到属性窗口

现在编写用于管理 ListBox 的代码,该代码将绑定到 “属性” 窗口。

必须更改按钮单击处理程序才能读取 TextBox、创建 TodoItem 并将其添加到 ListBox。

  1. 将现有 button1_Click 函数替换为创建一个新的 TodoItem 并将其添加到 ListBox 的代码。 它调用 TrackSelection(),稍后将定义。

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        if (textBox.Text.Length > 0)
        {
            var item = new TodoItem(this, textBox.Text);
            listBox.Items.Add(item);
            TrackSelection();
            CheckForErrors();
        }
    }
    
  2. 在“设计”视图中,选择 ListBox 控件。 在 “属性” 窗口中,单击 “事件处理程序 ”按钮并找到 SelectionChanged 事件。 使用listBox_SelectionChanged填充文本框。 执行此操作会为 SelectionChanged 处理程序添加存根,并将其分配给事件。

  3. 实现 TrackSelection() 方法。 由于需要获取 SVsUIShellSTrackSelection 服务,需要使 GetService TodoWindowControl 可访问。 将下列方法添加到 TodoWindow 类:

    internal object GetVsService(Type service)
    {
        return GetService(service);
    }
    
  4. 将以下 using 指令添加到 TodoWindowControl.xaml.cs

    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Shell;
    
  5. 按如下所示填写 SelectionChanged 处理程序:

    private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        TrackSelection();
    }
    
  6. 现在,填写 TrackSelection 函数,该函数将提供与“属性”窗口的集成。 当用户将项添加到 ListBox 或单击 ListBox 中的项时,将调用此函数。 它将 ListBox 的内容添加到 SelectionContainer,并将 SelectionContainer 传递给 Properties 窗口的 OnSelectChange 事件处理程序。 TrackSelection 服务跟踪用户界面(UI)中的选定对象并显示其属性

    private SelectionContainer mySelContainer;
    private System.Collections.ArrayList mySelItems;
    private IVsWindowFrame frame = null;
    
    private void TrackSelection()
    {
        if (frame == null)
        {
            var shell = parent.GetVsService(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 = listBox.SelectedItem as TodoItem;
        if (selected != null)
        {
            mySelItems.Add(selected);
        }
    
        mySelContainer.SelectedObjects = mySelItems;
    
        ITrackSelection track = parent.GetVsService(typeof(STrackSelection))
                                as ITrackSelection;
        if (track != null)
        {
            track.OnSelectChange(mySelContainer);
        }
    }
    

    有了“属性”窗口可以使用的类后,即可将属性”窗口与工具窗口集成。 当用户在工具窗口中的 ListBox 中单击某个项时, 应相应地更新“属性” 窗口。 同样,当用户在“属性”窗口中更改 ToDo 项时,应更新关联的项。

  7. 现在,在 TodoWindowControl.xaml.cs 中添加 UpdateList 函数代码的其余部分。 它应从 ListBox 中删除并重新添加修改后的 TodoItem。

    public void UpdateList(TodoItem item)
    {
        var index = listBox.SelectedIndex;
        listBox.Items.RemoveAt(index);
        listBox.Items.Insert(index, item);
        listBox.SelectedItem = index;
    }
    
  8. 测试代码。 生成项目并启动调试。 应显示实验实例。

  9. 打开“工具>选项”页。 应在左窗格中看到 ToDo 类别。 类别按字母顺序列出,因此在 Ts 下查看。

  10. Todo 选项页上,应会看到 DaysAhead 属性设置为 0。 将其更改为 2

  11. “视图”/“其他 Windows ”菜单上,打开 TodoWindow。 在文本框中键入 EndDate ,然后单击“ 添加”。

  12. 在列表框中,应会看到两天后的日期。

向“输出”窗口添加文本,并将项添加到任务列表

对于任务列表,你将创建一个类型为 Task 的新对象,然后通过调用Add任务列表的方法将该 Task 对象添加到任务列表中。 若要写入 “输出 ”窗口,请调用其 GetPane 方法以获取窗格对象,然后调用 OutputString 窗格对象的方法。

  1. TodoWindowControl.xaml.cs 中,button1_Click在方法中添加代码以获取“输出”窗口的“常规”窗格(默认值),并写入该窗口。 方法应如下所示:

    private void button1_Click(object sender, EventArgs e)
    {
        if (textBox.Text.Length > 0)
        {
            var item = new TodoItem(this, textBox.Text);
            listBox.Items.Add(item);
    
            var outputWindow = parent.GetVsService(
                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();
        }
    }
    
  2. 若要将项添加到任务列表,需要向 TodoWindowControl 类添加嵌套类。 嵌套类需要派生自 TaskProvider。 将以下代码添加到类的 TodoWindowControl 末尾。

    [Guid("72de1eAD-a00c-4f57-bff7-57edb162d0be")]
    public class TodoWindowTaskProvider : TaskProvider
    {
        public TodoWindowTaskProvider(IServiceProvider sp)
            : base(sp)
        {
        }
    }
    
  3. 接下来,向类添加专用引用 TodoTaskProviderCreateProvider() 方法 TodoWindowControl 。 代码应如下所示:

    private TodoWindowTaskProvider taskProvider;
    private void CreateProvider()
    {
        if (taskProvider == null)
        {
            taskProvider = new TodoWindowTaskProvider(parent);
            taskProvider.ProviderName = "To Do";
        }
    }
    
  4. 添加 ClearError(),这将清除任务列表,并将 ReportError()条目添加到任务列表,并将其添加到 TodoWindowControl 类。

    private void ClearError()
    {
        CreateProvider();
        taskProvider.Tasks.Clear();
    }
    private void ReportError(string p)
    {
        CreateProvider();
        var errorTask = new Task();
        errorTask.CanDelete = false;
        errorTask.Category = TaskCategory.Comments;
        errorTask.Text = p;
    
        taskProvider.Tasks.Add(errorTask);
    
        taskProvider.Show();
    
        var taskList = parent.GetVsService(typeof(SVsTaskList))
            as IVsTaskList2;
        if (taskList == null)
        {
            return;
        }
    
        var guidProvider = typeof(TodoWindowTaskProvider).GUID;
         taskList.SetActiveProvider(ref guidProvider);
    }
    
  5. 现在实现该方法 CheckForErrors ,如下所示。

    public void CheckForErrors()
    {
        foreach (TodoItem item in listBox.Items)
        {
            if (item.DueDate < DateTime.Now)
            {
                ReportError("To Do Item is out of date: "
                    + item.ToString());
            }
        }
    }
    

试试看

  1. 生成项目并启动调试。 这将显示实验实例。

  2. 打开 TodoWindow查看>其他 Windows>TodoWindow)。

  3. 在文本框中键入内容,然后单击“ 添加”。

    在今天添加到列表框中 2 天后的截止日期。 不会生成任何错误,并且任务列表(查看>任务列表)不应包含任何条目。

  4. 现在,将“工具>选项>ToDo”页上的设置2 更改为 0。

  5. TodoWindow 中键入其他内容,然后单击“ 添加 ”。 这会触发错误和任务列表中的条目

    添加项目时,初始日期设置为现在加上 2 天。

  6. “视图”菜单上,单击“输出打开“输出”窗口。

    请注意,每次添加项目时,“任务列表”窗格中都会显示一条消息。

  7. 单击 ListBox 中的某个项。

    属性” 窗口显示项的两个属性。

  8. 更改其中一个属性,然后按 Enter

    该项在 ListBox 中更新。