向工具窗口添加搜索

在扩展中创建或更新工具窗口时,可以添加在 Visual Studio 中的其他位置显示的相同搜索功能。 此功能包括以下功能:

  • 始终位于工具栏的自定义区域中的搜索框。

  • 覆盖在搜索框本身上的进度指示器。

  • 输入每个字符(即时搜索)或仅在选择 Enter 键(按需搜索)后显示结果的功能。

  • 显示最近搜索的术语的列表。

  • 能够按搜索目标的特定字段或方面筛选搜索。

通过执行本演练,你将了解如何执行以下任务:

  1. 创建 VSPackage 项目。

  2. 创建包含具有只读 TextBox 的 UserControl 的工具窗口。

  3. 将搜索框添加到工具窗口。

  4. 添加搜索实现。

  5. 启用即时搜索并显示进度栏。

  6. 添加“匹配大小写”选项。

  7. 仅添加 搜索偶数行 筛选器。

创建 VSIX 项目

  1. 创建一个名为 TestSearch 的工具窗口的 TestToolWindowSearchVSIX 项目。 如果需要执行此操作的帮助,请参阅 “使用工具窗口创建扩展”。

创建工具窗口

  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. 在菜单栏上,选择“查看>其他 Windows>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重写用于还原文本框状态的方法。 当用户取消搜索任务以及用户设置或取消设置选项或筛选器时,将调用此方法。 在 UI 线程上调用这两者 CreateSearchClearSearch 调用。 因此,无需通过 ThreadHelper.Invoke* 方法访问文本框。

    • 创建一个从中继承VsSearchTask的类TestSearchTask,该类提供默认实现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)。 代码将替代 ProvideSearchSettings 类中 TestSearch 的方法,这是更改默认设置所必需的。

    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. 选择筛选器。

    搜索框包含 行:“even”,并显示以下结果:

    2 良

    4 好

    6 再见

  7. 从搜索框中删除lines:"even",选择“匹配大小写”检查框,然后在搜索框中输入g

    将显示以下结果:

    1 go

    2 良

    5 再见

  8. 选择搜索框右侧的 X。

    清除搜索,并显示原始内容。 但是,仍选中“匹配大小写”检查框。