擴充屬性、工作清單、輸出和選項視窗
您可以在 Visual Studio 中存取任何工具視窗。 本逐步解說示範如何將工具視窗的相關資訊整合到新的 [選項] 頁面,以及 [屬性] 頁面上的新設定,以及如何寫入至 [工作清單] 和 [輸出] 視窗。
使用工具視窗建立擴充功能
使用 VSIX 範本建立名為 TodoList 的專案,並新增名為 TodoWindow 的自訂工具視窗項目範本。
注意
如需使用工具視窗建立擴充功能的詳細資訊,請參閱使用工具視窗建立擴充功能。
設定工具視窗
新增要在其中輸入新 ToDo 專案的 TextBox、將新專案新增至清單的 Button,以及要顯示清單上專案的 ListBox。
在 TodoWindow.xaml 中,從 UserControl 刪除 Button、TextBox 和 StackPanel 控制項。
注意
這不會刪除 button1_Click 事件處理常式,您會在稍後的步驟中重複使用該事件處理常式。
從 [工具箱] 的 [所有 WPF 控制項] 驅動,將 [畫布] 控制項拖曳至窗格。
將 TextBox、Button 和 ListBox 拖曳至 [畫布]。 排列元素,讓 TextBox 和 Button 位於相同的層級,而 ListBox 會填滿其下方的其餘視窗,如下圖所示。
在 XAML 窗格中,尋找 Button,並將其 [內容] 屬性設定為 [新增]。 藉由新增
Click="button1_Click"
屬性,將按鈕事件處理常式重新連接到 Button 控制項。 [畫布] 區塊此時應顯示如下:<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 中,將 ProvideOptionPageAttribute 新增至
TodoWindowPackage
類別:[ProvideOptionPage(typeof(ToolsOptions), "ToDo", "General", 101, 106, true)]
ProvideOptionPage 建構函式的第一個參數是您稍早建立的
ToolsOptions
類別類型。 第二個參數 「ToDo」 是 [選項] 對話方塊中的類別名稱。 第三個參數 「General」是 [選項] 對話方塊的子類別名稱,其中 [選項] 頁面可供使用。 接下來的兩個參數是字串的資源識別碼;第一個是類別的名稱,第二個是子類別的名稱。 最後一個參數會決定是否可以使用自動化來存取此頁面。當使用者開啟 [選項] 頁面時,它應該類似下圖。
請注意 ToDo 類別和 General 子類別。
將資料提供給 [屬性] 視窗
您可以建立名為 TodoItem
的類別,將個別專案的相關資訊儲存在 ToDo 清單中,讓 ToDo 清單資訊可供使用。
新增名為
TodoItem.cs
的類別。當使用者可以使用工具視窗時,ListBox 中的專案將由 TodoItems 表示。 當使用者在 ListBox 中選取其中一個專案時,[屬性] 視窗會顯示專案的相關資訊。
若要在 [屬性] 視窗中提供資料,您可以將資料轉換成具有兩個特殊屬性 (
Description
和Category
) 的公用屬性。Description
是出現在 [屬性] 視窗底部的文字。Category
會決定當 [屬性] 視窗顯示在 [分類] 檢視中的位置。 在下圖中,[屬性] 視窗位於 [分類] 檢視中,已選取 [待辦事項欄位] 類別中的 [名稱] 屬性,並在視窗底部顯示 [名稱] 屬性的描述。將下列 using 指示詞新增 TodoItem.cs 檔案。
using System.ComponentModel; using System.Windows.Forms; using Microsoft.VisualStudio.Shell.Interop;
將
public
存取修飾詞新增至類別宣告。public class TodoItem { }
新增兩個屬性:
Name
和DueDate
。 我們稍後將執行UpdateList()
和CheckForErrors()
。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 中,將虛設常式方法新增至
CheckForError
和UpdateList
方法的TodoWindowControl
類別。 將它們放在 ProcessDialogChar 之後,並在檔案結尾之前。public void CheckForErrors() { } public void UpdateList(TodoItem item) { }
CheckForError
方法會呼叫在上層物件中具有相同名稱的方法,而且該方法會檢查是否已發生任何錯誤,並正確處理錯誤。UpdateList
方法會更新上層控制項中的 ListBox;當這個類別中的Name
和DueDate
屬性變更時,會呼叫此方法。 稍後將實作它們。
整合到 [屬性] 視窗
現在,撰寫管理 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 服務,因此您需要讓 TodoWindowControl 可存取 GetService。 將下列方法新增至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 傳遞至 [屬性] 視窗的 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。在 [檢視 / 其他視窗] 功能表上,開啟 TodoWindow。 在文字方塊中輸入 EndDate,然後按一下 [新增]。
在清單方塊中,您應該會在兩天後看到比今天晚兩天的日期。
將文字新增至 [輸出] 視窗,並將項目新增至 [工作清單]
針對 [工作清單],您可以建立類型為 [工作] 的新物件,然後藉由呼叫其 Add
方法,將該 [工作] 物件新增至 [工作清單]。 若要寫入 [輸出] 視窗,您可以呼叫其 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 ([檢視]>[其他視窗]>TodoWindow)。
在文字方塊中輸入內容,然後按一下 [新增]。
到期日 (在今天之後的 2 天後) 會新增至清單方塊。 不會產生任何錯誤,而且 [工作清單] ([檢視]>[工作清單]) 不應該有任何項目。
現在,將 [工具]>[選項]>[待辦事項] 頁面上的設定從 2 變更回 0。
在 TodoWindow 中輸入其他內容,然後再次按一下 [新增]。 這會觸發錯誤,也會觸發 [工作清單] 中的項目。
當您新增專案時,初始日期會設定為現在加上 2 天。
在 [檢視] 功能表上,按一下 [輸出] 以開啟 [輸出] 視窗。
請注意,每次新增專案時,都會在 [工作清單] 窗格中顯示訊息。
按兩下 ListBox 中的其中一個項目。
[屬性] 視窗會顯示項目的兩個屬性。
變更其中一個屬性,然後按 Enter 鍵。
項目會在 ListBox 中更新。