도구 창에 검색 추가

확장에서 도구 창을 만들거나 업데이트할 때 Visual Studio의 다른 위치에 나타나는 것과 동일한 검색 기능을 추가할 수 있습니다. 기능에는 다음과 같은 기능이 포함되어 있습니다.

  • 항상 도구 모음의 사용자 지정 영역에 있는 검색 상자.

  • 검색 상자 자체에 오버레이되는 진행률 표시기.

  • 각 문자를 입력하는 즉시(즉시 검색) 또는 Enter 키를 선택한 후에만 결과를 표시하는 기능(요청 시 검색).

  • 가장 최근에 검색한 용어를 보여 주는 목록.

  • 검색 대상의 특정 필드 또는 측면별로 검색을 필터링하는 기능.

이 연습에서는 다음 작업을 수행하는 방법을 알아봅니다.

  1. VSPackage 프로젝트를 만듭니다.

  2. 읽기 전용 TextBox가 있는 UserControl이 포함된 도구 창을 만듭니다.

  3. 도구 창에 검색 상자를 추가합니다.

  4. 검색 구현을 추가합니다.

  5. 즉시 검색과 진행률 표시줄 표시를 사용하도록 설정합니다.

  6. 대/소문자 구분 옵션을 추가합니다.

  7. 짝수 줄만 검색 필터를 추가합니다.

VSIX 프로젝트를 만들려면

  1. TestSearch라는 도구 창을 사용하여 TestToolWindowSearch라는 VSIX 프로젝트를 만듭니다. 이 작업을 수행하는 데 도움이 필요한 경우 도구 창으로 확장 만들기를 참조하세요.

도구 창을 만들려면

  1. TestToolWindowSearch 프로젝트에서 TestSearchControl.xaml 파일을 엽니다.

  2. 기존 <StackPanel> 블록을 다음 블록으로 교체합니다. 그러면 도구 창의 UserControl에 읽기 전용 TextBox가 추가됩니다.

    <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 클래스에 추가합니다. 이 코드는 get 접근자가 true를 반환하도록 SearchEnabled 속성을 재정의합니다.

    검색을 사용하려면 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* 메서드를 사용하여 텍스트 상자에 액세스할 필요가 없습니다.

    • IVsSearchTask의 기본 구현을 제공하는 VsSearchTask에서 상속하는 TestSearchTask라는 클래스를 만듭니다.

      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 클래스에 추가합니다. 코드는 짝수 줄만 표시되도록 검색 결과를 필터링하도록 지정하는 WindowSearchSimpleFilter를 추가하여 SearchFiltersEnum을 구현합니다.

    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 파일에서 TestSearch 클래스에 있는 TestSearchTask 클래스에 다음 메서드를 추가합니다. 이러한 메서드는 다음 단계에서 수정할 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. 필터를 선택합니다.

    검색 상자에는 lines:"even"이 포함되며 다음 결과가 나타납니다.

    2 good

    4 Good

    6 Goodbye

  7. 검색 상자에서 lines:"even"을 삭제하고 대/소문자 구분 확인란을 선택한 다음 검색 상자에 g를 입력합니다.

    다음과 같은 결과가 나타납니다.

    1 go

    2 good

    5 goodbye

  8. 검색 상자의 오른쪽에 있는 X를 선택합니다.

    검색이 지워지고 원래 내용이 나타납니다. 그러나 대/소문자 구분 확인란은 여전히 선택되어 있습니다.