Ajouter une recherche à une fenêtre d’outil

Lorsque vous créez ou mettez à jour une fenêtre d’outil dans votre extension, vous pouvez ajouter la même fonctionnalité de recherche qui s’affiche ailleurs dans Visual Studio. Cette fonctionnalité inclut les fonctionnalités suivantes :

  • Zone de recherche toujours située dans une zone personnalisée de la barre d’outils.

  • Indicateur de progression superposé sur la zone de recherche elle-même.

  • Possibilité d’afficher les résultats dès que vous entrez chaque caractère (recherche instantanée) ou seulement après avoir choisi la clé Entrée (recherche à la demande).

  • Liste qui affiche les termes pour lesquels vous avez recherché le plus récemment.

  • Possibilité de filtrer les recherches par champs ou aspects spécifiques des cibles de recherche.

En suivant cette procédure pas à pas, vous allez apprendre à effectuer les tâches suivantes :

  1. Créez un projet VSPackage.

  2. Créez une fenêtre d’outil qui contient un UserControl avec une zone de texte en lecture seule.

  3. Ajoutez une zone de recherche à la fenêtre outil.

  4. Ajoutez l’implémentation de recherche.

  5. Activez la recherche instantanée et l’affichage d’une barre de progression.

  6. Ajouter une option Match Case .

  7. Ajoutez un filtre de lignes pairs de recherche uniquement .

Pour créer un projet VSIX

  1. Créez un projet VSIX nommé TestToolWindowSearch avec une fenêtre d’outil nommée TestSearch. Si vous avez besoin d’aide pour ce faire, consultez Création d’une extension avec une fenêtre d’outil.

Pour créer une fenêtre d’outil

  1. Dans le TestToolWindowSearch projet, ouvrez le fichier TestSearchControl.xaml .

  2. Remplacez le bloc existant <StackPanel> par le bloc suivant, qui ajoute une lecture seule TextBox à la UserControl fenêtre outil.

    <StackPanel Orientation="Vertical">
        <TextBox Name="resultsTextBox" Height="800.0"
            Width="800.0"
            IsReadOnly="True">
        </TextBox>
    </StackPanel>
    
  3. Dans le fichier TestSearchControl.xaml.cs , ajoutez la directive using suivante :

    using System.Text;
    
  4. Supprimez la button1_Click() méthode.

    Dans la classe TestSearchControl , ajoutez le code suivant.

    Ce code ajoute une propriété publique TextBox nommée SearchResultsTextBox et une propriété de chaîne publique nommée SearchContent. Dans le constructeur, SearchResultsTextBox est défini sur la zone de texte, et SearchContent est initialisé sur un jeu de chaînes délimité par une nouvelle ligne. Le contenu de la zone de texte est également initialisé sur l’ensemble de chaînes.

    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. Générez le projet et commencez le débogage. L’instance expérimentale de Visual Studio s’affiche.

  6. Dans la barre de menus, choisissez Afficher>d’autres Windows>TestSearch.

    La fenêtre outil s’affiche, mais le contrôle de recherche n’apparaît pas encore.

Pour ajouter une zone de recherche à la fenêtre outil

  1. Dans le fichier TestSearch.cs , ajoutez le code suivant à la TestSearch classe. Le code remplace la SearchEnabled propriété afin que l’accesseur get retourne true.

    Pour activer la recherche, vous devez remplacer la SearchEnabled propriété. La ToolWindowPane classe implémente IVsWindowSearch et fournit une implémentation par défaut qui n’active pas la recherche.

    public override bool SearchEnabled
    {
        get { return true; }
    }
    
  2. Générez le projet et commencez le débogage. L’instance expérimentale s’affiche.

  3. Dans l’instance expérimentale de Visual Studio, ouvrez TestSearch.

    En haut de la fenêtre outil, un contrôle de recherche s’affiche avec un filigrane de recherche et une icône de loupe. Toutefois, la recherche ne fonctionne pas encore, car le processus de recherche n’a pas encore été implémenté.

Pour ajouter l’implémentation de la recherche

Lorsque vous activez la recherche sur un ToolWindowPanehôte de recherche, comme dans la procédure précédente, la fenêtre outil crée un hôte de recherche. Cet hôte configure et gère les processus de recherche, qui se produisent toujours sur un thread d’arrière-plan. Étant donné que la ToolWindowPane classe gère la création de l’hôte de recherche et la configuration de la recherche, vous devez uniquement créer une tâche de recherche et fournir la méthode de recherche. Le processus de recherche se produit sur un thread d’arrière-plan et les appels au contrôle de fenêtre d’outil se produisent sur le thread d’interface utilisateur. Par conséquent, vous devez utiliser la méthode ThreadHelper.Invoke* pour gérer les appels que vous effectuez dans le traitement du contrôle.

  1. Dans le fichier TestSearch.cs , ajoutez les directives suivantes 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. Dans la TestSearch classe, ajoutez le code suivant, qui effectue les actions suivantes :

    • Remplace la CreateSearch méthode pour créer une tâche de recherche.

    • Remplace la méthode pour restaurer l’état ClearSearch de la zone de texte. Cette méthode est appelée lorsqu’un utilisateur annule une tâche de recherche et lorsqu’un utilisateur définit ou désactive les options ou filtres. Les deux CreateSearch et ClearSearch sont appelées sur le thread d’interface utilisateur. Par conséquent, vous n’avez pas besoin d’accéder à la zone de texte au moyen de la méthode ThreadHelper.Invoke* .

    • Crée une classe nommée TestSearchTask qui hérite de VsSearchTask, qui fournit une implémentation par défaut de IVsSearchTask.

      Dans TestSearchTask, le constructeur définit un champ privé qui fait référence à la fenêtre d’outil. Pour fournir la méthode de recherche, vous remplacez les méthodes et OnStopSearch les OnStartSearch méthodes. La OnStartSearch méthode est l’endroit où vous implémentez le processus de recherche. Ce processus inclut l’exécution de la recherche, l’affichage des résultats de recherche dans la zone de texte et l’appel de l’implémentation de classe de base de cette méthode pour signaler que la recherche est terminée.

    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. Testez votre implémentation de recherche en effectuant les étapes suivantes :

    1. Régénérez le projet et démarrez le débogage.

    2. Dans l’instance expérimentale de Visual Studio, ouvrez à nouveau la fenêtre outil, entrez du texte de recherche dans la fenêtre de recherche, puis cliquez sur Entrée.

      Les résultats corrects doivent apparaître.

Pour personnaliser le comportement de recherche

En modifiant les paramètres de recherche, vous pouvez apporter diverses modifications dans la façon dont le contrôle de recherche apparaît et la façon dont la recherche est effectuée. Par exemple, vous pouvez modifier le filigrane (le texte par défaut qui apparaît dans la zone de recherche), la largeur minimale et maximale du contrôle de recherche et indiquer s’il faut afficher une barre de progression. Vous pouvez également modifier le point auquel les résultats de recherche commencent à apparaître (à la demande ou à la recherche instantanée) et indique si vous souhaitez afficher une liste de termes pour lesquels vous avez récemment recherché. Vous trouverez la liste complète des paramètres dans la SearchSettingsDataSource classe.

  1. Dans le fichier* TestSearch.cs*, ajoutez le code suivant à la TestSearch classe. Ce code active la recherche instantanée au lieu de la recherche à la demande (ce qui signifie que l’utilisateur n’a pas besoin de cliquer sur Entrée). Le code remplace la ProvideSearchSettings méthode dans la TestSearch classe, qui est nécessaire pour modifier les paramètres par défaut.

    public override void ProvideSearchSettings(IVsUIDataSource pSearchSettings)
    {
        Utilities.SetValue(pSearchSettings,
            SearchSettingsDataSource.SearchStartTypeProperty.Name,
            (uint)VSSEARCHSTARTTYPE.SST_INSTANT);}
    
  2. Testez le nouveau paramètre en regénérer la solution et en redémarrant le débogueur.

    Les résultats de la recherche s’affichent chaque fois que vous entrez un caractère dans la zone de recherche.

  3. Dans la ProvideSearchSettings méthode, ajoutez la ligne suivante, qui permet l’affichage d’une barre de progression.

    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);
    }
    

    Pour que la barre de progression apparaisse, la progression doit être signalée. Pour signaler la progression, supprimez les marques de commentaire du code suivant dans la OnStartSearch méthode de la TestSearchTask classe :

    SearchCallback.ReportProgress(this, progress++, (uint)contentArr.GetLength(0));
    
  4. Pour ralentir le traitement suffisant pour que la barre de progression soit visible, supprimez les marques de commentaire de la ligne suivante dans la OnStartSearch méthode de la TestSearchTask classe :

    System.Threading.Thread.Sleep(100);
    
  5. Testez les nouveaux paramètres en regénérer la solution et en commençant à déboguer.

    La barre de progression s’affiche dans la fenêtre de recherche (sous la zone de texte de recherche) chaque fois que vous effectuez une recherche.

Pour permettre aux utilisateurs d’affiner leurs recherches

Vous pouvez permettre aux utilisateurs d’affiner leurs recherches à l’aide d’options telles que Match case ou Match entier. Les options peuvent être booléennes, qui apparaissent sous forme de zones case activée ou de commandes, qui apparaissent sous forme de boutons. Pour cette procédure pas à pas, vous allez créer une option booléenne.

  1. Dans le fichier TestSearch.cs , ajoutez le code suivant à la TestSearch classe. Le code remplace la méthode, ce qui permet à l’implémentation SearchOptionsEnum de la recherche de détecter si une option donnée est activée ou désactivée. Le code dans SearchOptionsEnum ajoute une option permettant de faire correspondre la casse à un IVsEnumWindowSearchOptions énumérateur. L’option de correspondance est également mise à disposition en tant que MatchCaseOption propriété.

    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. Dans la TestSearchTask classe, supprimez les marques de commentaire de la ligne suivante dans la OnStartSearch méthode :

    matchCase = m_toolWindow.MatchCaseOption.Value;
    
  3. Testez l’option :

    1. Générez le projet et commencez le débogage. L’instance expérimentale s’affiche.

    2. Dans la fenêtre outil, choisissez la flèche vers le bas sur le côté droit de la zone de texte.

      La case Correspondance case activée zone s’affiche.

    3. Sélectionnez la case Correspondance case activée zone, puis effectuez des recherches.

Pour ajouter un filtre de recherche

Vous pouvez ajouter des filtres de recherche qui permettent aux utilisateurs d’affiner l’ensemble des cibles de recherche. Par exemple, vous pouvez filtrer les fichiers dans Explorateur de fichiers par les dates sur lesquelles ils ont été modifiés le plus récemment et leurs extensions de nom de fichier. Dans cette procédure pas à pas, vous allez ajouter un filtre pour les lignes paires uniquement. Lorsque l’utilisateur choisit ce filtre, l’hôte de recherche ajoute les chaînes que vous spécifiez à la requête de recherche. Vous pouvez ensuite identifier ces chaînes à l’intérieur de votre méthode de recherche et filtrer les cibles de recherche en conséquence.

  1. Dans le fichier TestSearch.cs , ajoutez le code suivant à la TestSearch classe. Le code implémente SearchFiltersEnum en ajoutant un WindowSearchSimpleFilter spécifiant pour filtrer les résultats de recherche afin que seules les lignes s’affichent.

    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;
        }
    }
    
    

    Maintenant, le contrôle de recherche affiche le filtre Search even lines onlyde recherche. Lorsque l’utilisateur choisit le filtre, la chaîne lines:"even" apparaît dans la zone de recherche. D’autres critères de recherche peuvent apparaître en même temps que le filtre. Les chaînes de recherche peuvent apparaître avant le filtre, après le filtre ou les deux.

  2. Dans le fichier TestSearch.cs , ajoutez les méthodes suivantes à la TestSearchTask classe, qui se trouve dans la TestSearch classe. Ces méthodes prennent en charge la OnStartSearch méthode que vous allez modifier à l’étape suivante.

    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. Dans la TestSearchTask classe, mettez à jour la OnStartSearch méthode avec le code suivant. Cette modification met à jour le code pour prendre en charge le filtre.

    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. Testez votre code.

  5. Générez le projet et commencez le débogage. Dans l’instance expérimentale de Visual Studio, ouvrez la fenêtre outil, puis choisissez la flèche vers le bas sur le contrôle de recherche.

    La case Correspondance case activée zone et les lignes de recherche s’affichent uniquement.

  6. Choisissez le filtre.

    La zone de recherche contient des lignes : « même » et les résultats suivants s’affichent :

    2 bons

    4 Bon

    6 Adieu

  7. Supprimez lines:"even" de la zone de recherche, sélectionnez la case Correspondance case activée zone, puis entrez g dans la zone de recherche.

    Les résultats suivants s’affichent :

    1 go

    2 bons

    5 au revoir

  8. Choisissez le X sur le côté droit de la zone de recherche.

    La recherche est effacée et le contenu d’origine s’affiche. Toutefois, la case Correspondance case activée zone est toujours sélectionnée.