共用方式為


將搜尋新增至工具視窗

當您在延伸模組中建立或更新工具視窗時,您可以新增出現在 Visual Studio 中其他地方的相同搜尋功能。 此功能包含下列特色︰

  • 一個搜尋方塊,始終位於工具列自訂區域中。

  • 一個進度指示器,重疊顯示在搜尋方塊上。

  • 能夠在您輸入每個字元 (立即搜尋) 或選擇 Enter 鍵 (隨選搜尋) 後立即顯示結果。

  • 一份清單,顯示您最近搜尋的字詞。

  • 能夠依搜尋目標的特定欄位或層面篩選搜尋。

依照本逐步解說操作,您將學習如何執行下列工作:

  1. 建立 VSPackage 專案。

  2. 建立工具視窗,包含具有唯讀 TextBox 的 UserControl。

  3. 將搜尋方塊新增至工具視窗。

  4. 新增搜尋實作。

  5. 啟用立即搜尋並顯示進度列。

  6. 新增 [大小寫須相符] 選項。

  7. 新增 [僅搜尋偶數行] 篩選條件。

建立 VSIX 專案

  1. 建立一個名為 TestToolWindowSearch 的 VSIX 專案並將工具視窗命名為 TestSearch。 如果您需要協助來執行這項作業,請參閱使用工具視窗建立延伸模組

建立工具視窗

  1. TestToolWindowSearch 專案中,開啟 TestSearchControl.xaml 檔案。

  2. 將現有的 <StackPanel> 區塊取代為下列區塊,這會在工具視窗中將唯讀 TextBox 新增至 UserControl

    <StackPanel Orientation="Vertical">
        <TextBox Name="resultsTextBox" Height="800.0"
            Width="800.0"
            IsReadOnly="True">
        </TextBox>
    </StackPanel>
    
  3. 在 TestSearchControl.xaml.cs 檔案中,新增下列 using 指示詞:

    using System.Text;
    
  4. 移除 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();
        }
    }
    
  5. 建置此專案並開始偵錯。 隨即出現 Visual Studio 實驗執行個體。

  6. 在功能表列上,選擇 [檢視]>[其他視窗]>[TestSearch]

    隨即出現工具視窗,但搜尋控制項尚未出現。

將搜尋方塊新增至工具視窗

  1. 在 TestSearch.cs 檔案中,將下列程式碼新增到 TestSearch 類別。 該程式碼會覆寫 SearchEnabled 屬性,讓 get 存取子傳回 true

    若要啟用搜尋,您必須覆寫 SearchEnabled 屬性。 ToolWindowPane 類別會實作 IVsWindowSearch 並提供未啟用搜尋的預設實作。

    public override bool SearchEnabled
    {
        get { return true; }
    }
    
  2. 建置此專案並開始偵錯。 隨即出現實驗執行個體。

  3. 在 Visual Studio 的實驗執行個體中開啟 [TestSearch]

    在工具視窗頂端,搜尋控制項隨即出現,並包含「搜尋」浮水印和放大鏡圖示。 不過,搜尋還不能運作,因為尚未實作搜尋程序。

新增搜尋實作

當您在 ToolWindowPane 上啟用搜尋時,如上一個程序中所示,工具視窗會建立搜尋主機。 此主機會設定和管理搜尋處理序,始終都是發生在背景執行緒上。 因為 ToolWindowPane 類別會管理搜尋主機的建立和搜尋的設定,因此您只需要建立搜尋工作並提供搜尋方法。 搜尋處理序會發生在背景執行緒上,而對工具視窗控制項的呼叫則會發生在 UI 執行緒上。 因此,您必須使用 ThreadHelper.Invoke* 方法來管理您在處理控制項時所進行的任何呼叫。

  1. 在 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;
    
  2. TestSearch 類別中,新增下列程序碼,以執行下列動作:

    • 覆寫 CreateSearch 方法以建立搜尋工作。

    • 覆寫 ClearSearch 方法來還原文字方塊的狀態。 當使用者取消搜尋工作,以及當使用者設定或取消設定選項或篩選器時,就會呼叫這個方法。 CreateSearchClearSearch 都會在 UI 執行緒上呼叫。 因此,您不需要透過 ThreadHelper.Invoke* 方法來存取文字方塊。

    • 建立名為 TestSearchTask 的類別,這是繼承自 VsSearchTask,提供 IVsSearchTask 的預設實作。

      TestSearchTask 中,建構函式會設定一個參考工具視窗的私用欄位。 若要提供搜尋方法,您可以覆寫 OnStartSearchOnStopSearch 方法。 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;
        }
    }
    
  3. 執行下列步驟來測試搜尋實作:

    1. 重建此專案並開始偵錯。

    2. 在 Visual Studio 的實驗執行個體中,再次開啟工具視窗,在搜尋視窗中輸入一些搜尋文字,然後按一下 ENTER

      應該會出現正確的結果。

自訂搜尋行為

藉由變更搜尋設定,您可以對搜尋控制項的顯示方式和搜尋的執行方式進行各種變更。例如,您可以變更浮水印 (出現在搜尋方塊中的預設文字)、搜尋控制項的最小和最大寬度,以及是否要顯示進度列。 您也可以變更搜尋結果開始出現的點 (隨選或立即搜尋),以及是否要顯示最近搜尋的字詞清單。 您可以在 SearchSettingsDataSource 類別中找到完整的設定清單。

  1. 在 *TestSearch.cs* 檔案中,將下列程式碼新增到 TestSearch 類別。 此程式碼會啟用立即搜尋,而不是隨選搜尋 (這表示使用者不需要按一下 ENTER)。 該程式碼會覆寫 TestSearch 類別中的 ProvideSearchSettings 方法,這是變更預設設定的必要條件。

    public override void ProvideSearchSettings(IVsUIDataSource pSearchSettings)
    {
        Utilities.SetValue(pSearchSettings,
            SearchSettingsDataSource.SearchStartTypeProperty.Name,
            (uint)VSSEARCHSTARTTYPE.SST_INSTANT);}
    
  2. 重建方案並重新啟動偵錯工具,以測試新設定。

    您每次在搜尋方塊中輸入字元時,就會顯示搜尋結果。

  3. 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));
    
  4. 若要減慢處理速度而能夠看到進度列,請在 TestSearchTask 類別的 OnStartSearch 方法中取消註解下列這一行:

    System.Threading.Thread.Sleep(100);
    
  5. 藉由重建方案並開始偵錯來測試新設定。

    您每次執行搜尋時,進度列會出現在搜尋視窗中 (如搜尋文字方塊下方的藍色線條)。

讓使用者能夠精簡搜尋

您可以讓使用者透過 [大小寫須相符] 或 [全字拼寫須相符] 等選項來精簡搜尋。 選項可以是布林值 (顯示為核取方塊) 或命令 (顯示為按鈕)。 在本逐步解說中,您將建立布林選項。

  1. 在 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;
        }
    }
    
  2. TestSearchTask 類別中,取消註解 OnStartSearch 方法中的下列這一行:

    matchCase = m_toolWindow.MatchCaseOption.Value;
    
  3. 測試選項:

    1. 建置此專案並開始偵錯。 隨即出現實驗執行個體。

    2. 在工具視窗中,選擇文字方塊右側的向下箭號。

      [大小寫須相符] 核取方塊隨即出現。

    3. 選取 [大小寫須相符] 核取方塊,然後執行一些搜尋。

新增搜尋篩選條件

您可以新增搜尋篩選條件,讓使用者精簡一組搜尋目標。 例如,您可以依最近修改的日期及其副檔名,篩選檔案總管中的檔案。 在本逐步解說中,您會新增一個僅限偶數行的篩選條件。 當使用者選擇該篩選條件時,搜尋主機會將您指定的字串新增至搜尋查詢。 然後,您可以在搜尋方法內識別這些字串,並據以篩選搜尋目標。

  1. 在 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" 會出現在搜尋方塊中。 其他搜尋準則可以與篩選條件同時出現。 搜尋字串可能會出現在篩選條件之前、篩選條件之後,或兩者。

  2. 在 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;
    }
    
  3. 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();
    }
    
  4. 測試您的程式碼。

  5. 建置此專案並開始偵錯。 在 Visual Studio 的實驗執行個體中,開啟工具視窗,然後選擇搜尋控制項上的向下箭號。

    隨即出現 [大小寫須相符] 核取方塊和 [僅搜尋偶數行] 篩選條件。

  6. 選擇該篩選條件。

    搜尋方塊包含 [行: "偶數"],且會出現下列結果:

    2 good

    4 Good

    6 Goodbye

  7. 從搜尋方塊中刪除 lines:"even",選取 [大小寫須相符] 核取方塊,然後在搜尋方塊中輸入 g

    隨即出現下列結果:

    1 go

    2 good

    5 goodbye

  8. 選擇搜尋方塊右側的 X。

    會清除搜尋,並顯示原始內容。 不過,[大小寫須相符] 核取方塊仍然處於選取狀態。