將搜尋新增至工具視窗
當您在延伸模組中建立或更新工具視窗時,您可以新增出現在 Visual Studio 中其他地方的相同搜尋功能。 此功能包含下列特色︰
一個搜尋方塊,始終位於工具列自訂區域中。
一個進度指示器,重疊顯示在搜尋方塊上。
能夠在您輸入每個字元 (立即搜尋) 或選擇 Enter 鍵 (隨選搜尋) 後立即顯示結果。
一份清單,顯示您最近搜尋的字詞。
能夠依搜尋目標的特定欄位或層面篩選搜尋。
依照本逐步解說操作,您將學習如何執行下列工作:
建立 VSPackage 專案。
建立工具視窗,包含具有唯讀 TextBox 的 UserControl。
將搜尋方塊新增至工具視窗。
新增搜尋實作。
啟用立即搜尋並顯示進度列。
新增 [大小寫須相符] 選項。
新增 [僅搜尋偶數行] 篩選條件。
建立 VSIX 專案
- 建立一個名為
TestToolWindowSearch
的 VSIX 專案並將工具視窗命名為 TestSearch。 如果您需要協助來執行這項作業,請參閱使用工具視窗建立延伸模組。
建立工具視窗
在
TestToolWindowSearch
專案中,開啟 TestSearchControl.xaml 檔案。將現有的
<StackPanel>
區塊取代為下列區塊,這會在工具視窗中將唯讀 TextBox 新增至 UserControl。<StackPanel Orientation="Vertical"> <TextBox Name="resultsTextBox" Height="800.0" Width="800.0" IsReadOnly="True"> </TextBox> </StackPanel>
在 TestSearchControl.xaml.cs 檔案中,新增下列 using 指示詞:
using System.Text;
移除
button1_Click()
方法。在 TestSearchControl 類別中,新增下列程式碼。
此程式碼會新增名為 SearchResultsTextBox 的公用 TextBox 屬性,以及名為 SearchContent 的公用字串屬性。 在建構函式中,SearchResultsTextBox 會設定為文字方塊,而 SearchContent 會初始化為以換行符號分隔的一組字串。 文字方塊的內容也會初始化為一組字串。
public partial class MyControl : UserControl { public TextBox SearchResultsTextBox { get; set; } public string SearchContent { get; set; } public MyControl() { InitializeComponent(); this.SearchResultsTextBox = resultsTextBox; this.SearchContent = BuildContent(); this.SearchResultsTextBox.Text = this.SearchContent; } private string BuildContent() { StringBuilder sb = new StringBuilder(); sb.AppendLine("1 go"); sb.AppendLine("2 good"); sb.AppendLine("3 Go"); sb.AppendLine("4 Good"); sb.AppendLine("5 goodbye"); sb.AppendLine("6 Goodbye"); return sb.ToString(); } }
建置此專案並開始偵錯。 隨即出現 Visual Studio 實驗執行個體。
在功能表列上,選擇 [檢視]>[其他視窗]>[TestSearch]。
隨即出現工具視窗,但搜尋控制項尚未出現。
將搜尋方塊新增至工具視窗
在 TestSearch.cs 檔案中,將下列程式碼新增到
TestSearch
類別。 該程式碼會覆寫 SearchEnabled 屬性,讓 get 存取子傳回true
。若要啟用搜尋,您必須覆寫 SearchEnabled 屬性。 ToolWindowPane 類別會實作 IVsWindowSearch 並提供未啟用搜尋的預設實作。
public override bool SearchEnabled { get { return true; } }
建置此專案並開始偵錯。 隨即出現實驗執行個體。
在 Visual Studio 的實驗執行個體中開啟 [TestSearch]。
在工具視窗頂端,搜尋控制項隨即出現,並包含「搜尋」浮水印和放大鏡圖示。 不過,搜尋還不能運作,因為尚未實作搜尋程序。
新增搜尋實作
當您在 ToolWindowPane 上啟用搜尋時,如上一個程序中所示,工具視窗會建立搜尋主機。 此主機會設定和管理搜尋處理序,始終都是發生在背景執行緒上。 因為 ToolWindowPane 類別會管理搜尋主機的建立和搜尋的設定,因此您只需要建立搜尋工作並提供搜尋方法。 搜尋處理序會發生在背景執行緒上,而對工具視窗控制項的呼叫則會發生在 UI 執行緒上。 因此,您必須使用 ThreadHelper.Invoke* 方法來管理您在處理控制項時所進行的任何呼叫。
在 TestSearch.cs 檔案中,加入下列
using
指示詞。using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Windows.Controls; using Microsoft.Internal.VisualStudio.PlatformUI; using Microsoft.VisualStudio; using Microsoft.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop;
在
TestSearch
類別中,新增下列程序碼,以執行下列動作:覆寫 CreateSearch 方法以建立搜尋工作。
覆寫 ClearSearch 方法來還原文字方塊的狀態。 當使用者取消搜尋工作,以及當使用者設定或取消設定選項或篩選器時,就會呼叫這個方法。 CreateSearch 和 ClearSearch 都會在 UI 執行緒上呼叫。 因此,您不需要透過 ThreadHelper.Invoke* 方法來存取文字方塊。
建立名為
TestSearchTask
的類別,這是繼承自 VsSearchTask,提供 IVsSearchTask 的預設實作。在
TestSearchTask
中,建構函式會設定一個參考工具視窗的私用欄位。 若要提供搜尋方法,您可以覆寫 OnStartSearch 和 OnStopSearch 方法。 OnStartSearch 方法是您實作搜尋處理序的位置。 此處理序包括執行搜尋,在文字方塊中顯示搜尋結果,以及呼叫此方法的基底類別實作,以報告搜尋已完成。
public override IVsSearchTask CreateSearch(uint dwCookie, IVsSearchQuery pSearchQuery, IVsSearchCallback pSearchCallback) { if (pSearchQuery == null || pSearchCallback == null) return null; return new TestSearchTask(dwCookie, pSearchQuery, pSearchCallback, this); } public override void ClearSearch() { TestSearchControl control = (TestSearchControl)this.Content; control.SearchResultsTextBox.Text = control.SearchContent; } internal class TestSearchTask : VsSearchTask { private TestSearch m_toolWindow; public TestSearchTask(uint dwCookie, IVsSearchQuery pSearchQuery, IVsSearchCallback pSearchCallback, TestSearch toolwindow) : base(dwCookie, pSearchQuery, pSearchCallback) { m_toolWindow = toolwindow; } protected override void OnStartSearch() { // Use the original content of the text box as the target of the search. var separator = new string[] { Environment.NewLine }; TestSearchControl control = (TestSearchControl)m_toolWindow.Content; string[] contentArr = control.SearchContent.Split(separator, StringSplitOptions.None); // Get the search option. bool matchCase = false; // matchCase = m_toolWindow.MatchCaseOption.Value; // Set variables that are used in the finally block. StringBuilder sb = new StringBuilder(""); uint resultCount = 0; this.ErrorCode = VSConstants.S_OK; try { string searchString = this.SearchQuery.SearchString; // Determine the results. uint progress = 0; foreach (string line in contentArr) { if (matchCase == true) { if (line.Contains(searchString)) { sb.AppendLine(line); resultCount++; } } else { if (line.ToLower().Contains(searchString.ToLower())) { sb.AppendLine(line); resultCount++; } } // SearchCallback.ReportProgress(this, progress++, (uint)contentArr.GetLength(0)); // Uncomment the following line to demonstrate the progress bar. // System.Threading.Thread.Sleep(100); } } catch (Exception e) { this.ErrorCode = VSConstants.E_FAIL; } finally { ThreadHelper.Generic.Invoke(() => { ((TextBox)((TestSearchControl)m_toolWindow.Content).SearchResultsTextBox).Text = sb.ToString(); }); this.SearchResults = resultCount; } // Call the implementation of this method in the base class. // This sets the task status to complete and reports task completion. base.OnStartSearch(); } protected override void OnStopSearch() { this.SearchResults = 0; } }
執行下列步驟來測試搜尋實作:
重建此專案並開始偵錯。
在 Visual Studio 的實驗執行個體中,再次開啟工具視窗,在搜尋視窗中輸入一些搜尋文字,然後按一下 ENTER。
應該會出現正確的結果。
自訂搜尋行為
藉由變更搜尋設定,您可以對搜尋控制項的顯示方式和搜尋的執行方式進行各種變更。例如,您可以變更浮水印 (出現在搜尋方塊中的預設文字)、搜尋控制項的最小和最大寬度,以及是否要顯示進度列。 您也可以變更搜尋結果開始出現的點 (隨選或立即搜尋),以及是否要顯示最近搜尋的字詞清單。 您可以在 SearchSettingsDataSource 類別中找到完整的設定清單。
在 *TestSearch.cs* 檔案中,將下列程式碼新增到
TestSearch
類別。 此程式碼會啟用立即搜尋,而不是隨選搜尋 (這表示使用者不需要按一下 ENTER)。 該程式碼會覆寫TestSearch
類別中的ProvideSearchSettings
方法,這是變更預設設定的必要條件。public override void ProvideSearchSettings(IVsUIDataSource pSearchSettings) { Utilities.SetValue(pSearchSettings, SearchSettingsDataSource.SearchStartTypeProperty.Name, (uint)VSSEARCHSTARTTYPE.SST_INSTANT);}
重建方案並重新啟動偵錯工具,以測試新設定。
您每次在搜尋方塊中輸入字元時,就會顯示搜尋結果。
在
ProvideSearchSettings
方法中,新增下列一行,以啟用進度列的顯示。public override void ProvideSearchSettings(IVsUIDataSource pSearchSettings) { Utilities.SetValue(pSearchSettings, SearchSettingsDataSource.SearchStartTypeProperty.Name, (uint)VSSEARCHSTARTTYPE.SST_INSTANT); Utilities.SetValue(pSearchSettings, SearchSettingsDataSource.SearchProgressTypeProperty.Name, (uint)VSSEARCHPROGRESSTYPE.SPT_DETERMINATE); }
若要顯示進度列,必須報告進度。 若要報告進度,請在
TestSearchTask
類別的OnStartSearch
方法中取消註解下列程式碼:SearchCallback.ReportProgress(this, progress++, (uint)contentArr.GetLength(0));
若要減慢處理速度而能夠看到進度列,請在
TestSearchTask
類別的OnStartSearch
方法中取消註解下列這一行:System.Threading.Thread.Sleep(100);
藉由重建方案並開始偵錯來測試新設定。
您每次執行搜尋時,進度列會出現在搜尋視窗中 (如搜尋文字方塊下方的藍色線條)。
讓使用者能夠精簡搜尋
您可以讓使用者透過 [大小寫須相符] 或 [全字拼寫須相符] 等選項來精簡搜尋。 選項可以是布林值 (顯示為核取方塊) 或命令 (顯示為按鈕)。 在本逐步解說中,您將建立布林選項。
在 TestSearch.cs 檔案中,將下列程式碼新增到
TestSearch
類別。 該程式碼會覆寫SearchOptionsEnum
方法,讓搜尋實作偵測指定的選項是否開啟或關閉。SearchOptionsEnum
中的程式碼會新增選項,將大小寫與 IVsEnumWindowSearchOptions 列舉值比對。 比對大小寫的選項也做為MatchCaseOption
屬性提供。private IVsEnumWindowSearchOptions m_optionsEnum; public override IVsEnumWindowSearchOptions SearchOptionsEnum { get { if (m_optionsEnum == null) { List<IVsWindowSearchOption> list = new List<IVsWindowSearchOption>(); list.Add(this.MatchCaseOption); m_optionsEnum = new WindowSearchOptionEnumerator(list) as IVsEnumWindowSearchOptions; } return m_optionsEnum; } } private WindowSearchBooleanOption m_matchCaseOption; public WindowSearchBooleanOption MatchCaseOption { get { if (m_matchCaseOption == null) { m_matchCaseOption = new WindowSearchBooleanOption("Match case", "Match case", false); } return m_matchCaseOption; } }
在
TestSearchTask
類別中,取消註解OnStartSearch
方法中的下列這一行:matchCase = m_toolWindow.MatchCaseOption.Value;
測試選項:
建置此專案並開始偵錯。 隨即出現實驗執行個體。
在工具視窗中,選擇文字方塊右側的向下箭號。
[大小寫須相符] 核取方塊隨即出現。
選取 [大小寫須相符] 核取方塊,然後執行一些搜尋。
新增搜尋篩選條件
您可以新增搜尋篩選條件,讓使用者精簡一組搜尋目標。 例如,您可以依最近修改的日期及其副檔名,篩選檔案總管中的檔案。 在本逐步解說中,您會新增一個僅限偶數行的篩選條件。 當使用者選擇該篩選條件時,搜尋主機會將您指定的字串新增至搜尋查詢。 然後,您可以在搜尋方法內識別這些字串,並據以篩選搜尋目標。
在 TestSearch.cs 檔案中,將下列程式碼新增到
TestSearch
類別。 該程式碼會實作SearchFiltersEnum
,方法是新增 WindowSearchSimpleFilter,這會指定篩選搜尋結果而只顯示偶數行。public override IVsEnumWindowSearchFilters SearchFiltersEnum { get { List<IVsWindowSearchFilter> list = new List<IVsWindowSearchFilter>(); list.Add(new WindowSearchSimpleFilter("Search even lines only", "Search even lines only", "lines", "even")); return new WindowSearchFilterEnumerator(list) as IVsEnumWindowSearchFilters; } }
現在搜尋控制項會顯示搜尋篩選條件
Search even lines only
。 當使用者選擇篩選條件時,字串lines:"even"
會出現在搜尋方塊中。 其他搜尋準則可以與篩選條件同時出現。 搜尋字串可能會出現在篩選條件之前、篩選條件之後,或兩者。在 TestSearch.cs 檔案中,將下列方法新增至
TestSearchTask
類別,其位於TestSearch
類別中。 這些方法支援OnStartSearch
方法,您將在下一個步驟中修改此方法。private string RemoveFromString(string origString, string stringToRemove) { int index = origString.IndexOf(stringToRemove); if (index == -1) return origString; else return (origString.Substring(0, index) + origString.Substring(index + stringToRemove.Length)).Trim(); } private string[] GetEvenItems(string[] contentArr) { int length = contentArr.Length / 2; string[] evenContentArr = new string[length]; int indexB = 0; for (int index = 1; index < contentArr.Length; index += 2) { evenContentArr[indexB] = contentArr[index]; indexB++; } return evenContentArr; }
在
TestSearchTask
類別中,以下列程式碼更新OnStartSearch
方法。 這項變更會更新程式碼以支援該篩選條件。protected override void OnStartSearch() { // Use the original content of the text box as the target of the search. var separator = new string[] { Environment.NewLine }; string[] contentArr = ((TestSearchControl)m_toolWindow.Content).SearchContent.Split(separator, StringSplitOptions.None); // Get the search option. bool matchCase = false; matchCase = m_toolWindow.MatchCaseOption.Value; // Set variables that are used in the finally block. StringBuilder sb = new StringBuilder(""); uint resultCount = 0; this.ErrorCode = VSConstants.S_OK; try { string searchString = this.SearchQuery.SearchString; // If the search string contains the filter string, filter the content array. string filterString = "lines:\"even\""; if (this.SearchQuery.SearchString.Contains(filterString)) { // Retain only the even items in the array. contentArr = GetEvenItems(contentArr); // Remove 'lines:"even"' from the search string. searchString = RemoveFromString(searchString, filterString); } // Determine the results. uint progress = 0; foreach (string line in contentArr) { if (matchCase == true) { if (line.Contains(searchString)) { sb.AppendLine(line); resultCount++; } } else { if (line.ToLower().Contains(searchString.ToLower())) { sb.AppendLine(line); resultCount++; } } SearchCallback.ReportProgress(this, progress++, (uint)contentArr.GetLength(0)); // Uncomment the following line to demonstrate the progress bar. // System.Threading.Thread.Sleep(100); } } catch (Exception e) { this.ErrorCode = VSConstants.E_FAIL; } finally { ThreadHelper.Generic.Invoke(() => { ((TextBox)((TestSearchControl)m_toolWindow.Content).SearchResultsTextBox).Text = sb.ToString(); }); this.SearchResults = resultCount; } // Call the implementation of this method in the base class. // This sets the task status to complete and reports task completion. base.OnStartSearch(); }
測試您的程式碼。
建置此專案並開始偵錯。 在 Visual Studio 的實驗執行個體中,開啟工具視窗,然後選擇搜尋控制項上的向下箭號。
隨即出現 [大小寫須相符] 核取方塊和 [僅搜尋偶數行] 篩選條件。
選擇該篩選條件。
搜尋方塊包含 [行: "偶數"],且會出現下列結果:
2 good
4 Good
6 Goodbye
從搜尋方塊中刪除
lines:"even"
,選取 [大小寫須相符] 核取方塊,然後在搜尋方塊中輸入g
。隨即出現下列結果:
1 go
2 good
5 goodbye
選擇搜尋方塊右側的 X。
會清除搜尋,並顯示原始內容。 不過,[大小寫須相符] 核取方塊仍然處於選取狀態。