可以在 Visual Studio 中访问任何工具窗口。 本演练演示如何将有关工具窗口的信息集成到新的“选项”页和“属性”页上的新设置中,以及如何写入“任务列表”和“输出”窗口。
使用工具窗口创建扩展
使用 VSIX 模板创建名为 TodoList 的项目,并添加名为 TodoWindow 的自定义工具窗口项模板。
注意
有关使用工具窗口创建扩展的详细信息,请参阅 使用工具窗口创建扩展。
设置工具窗口
添加用于键入新 ToDo 项的 TextBox、向列表中添加新项的按钮,以及用于显示列表上的项的 ListBox。
在 TodoWindow.xaml 中,从 UserControl 中删除 Button、TextBox 和 StackPanel 控件。
注意
这不会删除 button1_Click 事件处理程序,将在后面的步骤中重复使用该事件处理程序。
从工具箱的“所有 WPF 控件”部分,将画布控件拖到网格。
将 TextBox、 按钮和 ListBox 拖到画布上。 排列元素,使 TextBox 和 Button 位于同一级别,ListBox 将填充其下方的其余窗口,如下图所示。

在 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>
自定义构造函数
在 TodoWindowControl.xaml.cs 文件中,添加以下 using 指令:
using System;添加对 TodoWindow 的公共引用,并让 TodoWindowControl 构造函数采用 TodoWindow 参数。 代码应如下所示:
public TodoWindow parent; public TodoWindowControl(TodoWindow window) { InitializeComponent(); parent = window; }在 TodoWindow.cs 中,将 TodoWindowControl 构造函数更改为包含 TodoWindow 参数。 代码应如下所示:
public TodoWindow() : base(null) { this.Caption = "TodoWindow"; this.BitmapResourceID = 301; this.BitmapIndex = 1; this.Content = new TodoWindowControl(this); }
“创建选项”页
可以在“选项”对话框中提供页面,以便用户可以更改工具窗口的设置。 创建“选项”页需要描述 TodoListPackage.cs 或 TodoListPackage.vb 文件中的选项和条目的类。
添加名为的
ToolsOptions.cs的类。ToolsOptions使类继承自 DialogPage.class ToolsOptions : DialogPage { }添加以下 using 指令:
using Microsoft.VisualStudio.Shell;本演练中的“选项”页仅提供一个名为 DaysAhead 的选项。 将名为 daysAhead 的私有字段和名为 DaysAhead 的属性添加到
ToolsOptions类:private double daysAhead; public double DaysAhead { get { return daysAhead; } set { daysAhead = value; } }现在,必须让项目了解此“选项”页。
使“选项”页可供用户使用
在 TodoWindowPackage.cs 中,向类添加 a ProvideOptionPageAttribute
TodoWindowPackage:[ProvideOptionPage(typeof(ToolsOptions), "ToDo", "General", 101, 106, true)]ProvideOptionPage 构造函数的第一个参数是前面创建的类
ToolsOptions的类型。 第二个参数“ToDo”是“选项”对话框中类别的名称。 第三个参数“常规”是“选项”对话框的子类别的名称,其中“选项”页将可用。 接下来的两个参数是字符串的资源 ID;第一个是类别的名称,第二个是子类别的名称。 最后一个参数确定是否可以使用自动化访问此页面。当用户打开“选项”页面时,它应如下图所示。

请注意类别 ToDo 和子类别 常规。
使数据可供属性窗口使用
可以通过创建一个名为将 TodoItem 有关单个项的信息存储在 ToDo 列表中的类来提供 ToDo 列表信息。
添加名为的
TodoItem.cs的类。当工具窗口可供用户使用时,ListBox 中的项将由 TodoItems 表示。 当用户在 ListBox 中选择其中一个项目时,“ 属性” 窗口将显示有关该项的信息。
若要在 “属性” 窗口中提供数据,可将数据转换为具有两个特殊属性的公共属性,
Description以及Category。Description是显示在“属性”窗口底部的文本。Category确定当“属性”窗口显示在“分类”视图中时应显示该属性的位置。 在下图中,“属性”窗口位于“分类”视图中,选择了“ToDo 字段”类别中的“名称”属性,并在窗口底部显示 Name 属性的说明。
添加以下 using 指令 TodoItem.cs 文件。
using System.ComponentModel; using System.Windows.Forms; using Microsoft.VisualStudio.Shell.Interop;将
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(); } } }向用户控件添加专用引用。 添加一个构造函数,该构造函数采用用户控件和此 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); }由于类的
TodoItem实例将存储在 ListBox 中,ListBox 将调用该ToString函数,因此必须重载该ToString函数。 将以下代码添加到 TodoItem.cs,在构造函数之后和类末尾之前。public override string ToString() { return name + " Due: " + dueDate.ToShortDateString(); }在 TodoWindowControl.xaml.cs 中,向类
CheckForErrorUpdateList中添加存根方法和TodoWindowControl方法。 将它们放在 ProcessDialogChar 和文件末尾之前。public void CheckForErrors() { } public void UpdateList(TodoItem item) { }该方法
CheckForError将调用父对象中同名的方法,该方法将检查是否发生了任何错误并正确处理它们。 该方法UpdateList将更新父控件中的 ListBox;此方法在此类中的属性DueDate更改时Name调用。 稍后将实施它们。
集成到属性窗口
现在编写用于管理 ListBox 的代码,该代码将绑定到 “属性” 窗口。
必须更改按钮单击处理程序才能读取 TextBox、创建 TodoItem 并将其添加到 ListBox。
将现有
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(); } }在“设计”视图中,选择 ListBox 控件。 在 “属性” 窗口中,单击 “事件处理程序 ”按钮并找到 SelectionChanged 事件。 使用listBox_SelectionChanged填充文本框。 执行此操作会为 SelectionChanged 处理程序添加存根,并将其分配给事件。
实现
TrackSelection()方法。 由于需要获取 SVsUIShellSTrackSelection 服务,需要使 GetService TodoWindowControl 可访问。 将下列方法添加到TodoWindow类:internal object GetVsService(Type service) { return GetService(service); }将以下 using 指令添加到 TodoWindowControl.xaml.cs:
using System.Runtime.InteropServices; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell;按如下所示填写 SelectionChanged 处理程序:
private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { TrackSelection(); }现在,填写 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 项时,应更新关联的项。
现在,在 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; }测试代码。 生成项目并启动调试。 应显示实验实例。
打开“工具>选项”页。 应在左窗格中看到 ToDo 类别。 类别按字母顺序列出,因此在 Ts 下查看。
在 Todo 选项页上,应会看到
DaysAhead属性设置为 0。 将其更改为 2。在 “视图”/“其他 Windows ”菜单上,打开 TodoWindow。 在文本框中键入 EndDate ,然后单击“ 添加”。
在列表框中,应会看到两天后的日期。
向“输出”窗口添加文本,并将项添加到任务列表
对于任务列表,你将创建一个类型为 Task 的新对象,然后通过调用Add任务列表的方法将该 Task 对象添加到任务列表中。 若要写入 “输出 ”窗口,请调用其 GetPane 方法以获取窗格对象,然后调用 OutputString 窗格对象的方法。
在 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(); } }若要将项添加到任务列表,需要向 TodoWindowControl 类添加嵌套类。 嵌套类需要派生自 TaskProvider。 将以下代码添加到类的
TodoWindowControl末尾。[Guid("72de1eAD-a00c-4f57-bff7-57edb162d0be")] public class TodoWindowTaskProvider : TaskProvider { public TodoWindowTaskProvider(IServiceProvider sp) : base(sp) { } }接下来,向类添加专用引用
TodoTaskProvider和CreateProvider()方法TodoWindowControl。 代码应如下所示:private TodoWindowTaskProvider taskProvider; private void CreateProvider() { if (taskProvider == null) { taskProvider = new TodoWindowTaskProvider(parent); taskProvider.ProviderName = "To Do"; } }添加
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); }现在实现该方法
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()); } } }
试试看
生成项目并启动调试。 这将显示实验实例。
打开 TodoWindow(查看>其他 Windows>TodoWindow)。
在文本框中键入内容,然后单击“ 添加”。
在今天添加到列表框中 2 天后的截止日期。 不会生成任何错误,并且任务列表(查看>任务列表)不应包含任何条目。
现在,将“工具>选项>ToDo”页上的设置从 2 更改为 0。
在 TodoWindow 中键入其他内容,然后单击“ 添加 ”。 这会触发错误和任务列表中的条目。
添加项目时,初始日期设置为现在加上 2 天。
在“视图”菜单上,单击“输出”打开“输出”窗口。
请注意,每次添加项目时,“任务列表”窗格中都会显示一条消息。
单击 ListBox 中的某个项。
“ 属性” 窗口显示项的两个属性。
更改其中一个属性,然后按 Enter。
该项在 ListBox 中更新。