Procédure pas à pas : mettre en surbrillance le texte
Vous pouvez ajouter différents effets visuels à l’éditeur en créant des composants MEF (Managed Extensibility Framework). Cette procédure pas à pas montre comment mettre en évidence chaque occurrence du mot actuel dans un fichier texte. Si un mot se produit plus d’une fois dans un fichier texte et que vous positionnez le point d’insertion dans une occurrence, chaque occurrence est mise en surbrillance.
Créer un projet MEF
Créez un projet VSIX C#. (Dans le Boîte de dialogue Nouveau projet , sélectionnez Visual C# / Extensibilité, puis VSIX Project.) Nommez la solution
HighlightWordTest
.Ajoutez un modèle d’élément Classifieur d’éditeur au projet. Pour plus d’informations, consultez Créer une extension avec un modèle d’élément d’éditeur.
Supprimez les fichiers de classe existants.
Définir un TextMarkerTag
La première étape de la mise en surbrillance du texte consiste à sous-classe TextMarkerTag et à définir son apparence.
Pour définir un TextMarkerTag et un MarkerFormatDefinition
Ajoutez un fichier de classe et nommez-le HighlightWordTag.
Ajoutez les références suivantes :
Microsoft.VisualStudio.CoreUtility
Microsoft.VisualStudio.Text.Data
Microsoft.VisualStudio.Text.Logic
Microsoft.VisualStudio.Text.UI
Microsoft.VisualStudio.Text.UI.Wpf
System.ComponentModel.Composition
Presentation.Core
Presentation.Framework
Importez les espaces de noms suivants.
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Threading; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; using System.Windows.Media;
Créez une classe qui hérite et TextMarkerTag nommez-la
HighlightWordTag
.internal class HighlightWordTag : TextMarkerTag { }
Créez une deuxième classe qui hérite de MarkerFormatDefinition, et nommez-la
HighlightWordFormatDefinition
. Pour utiliser cette définition de format pour votre balise, vous devez l’exporter avec les attributs suivants :NameAttribute: les balises l’utilisent pour référencer ce format
UserVisibleAttribute: cela entraîne l’affichage du format dans l’interface utilisateur
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition { }
Dans le constructeur pour HighlightWordFormatDefinition, définissez son nom d’affichage et son apparence. La propriété Background définit la couleur de remplissage, tandis que la propriété de premier plan définit la couleur de bordure.
public HighlightWordFormatDefinition() { this.BackgroundColor = Colors.LightBlue; this.ForegroundColor = Colors.DarkBlue; this.DisplayName = "Highlight Word"; this.ZOrder = 5; }
Dans le constructeur de HighlightWordTag, transmettez le nom de la définition de format que vous avez créée.
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
Implémenter un ITagger
L’étape suivante consiste à implémenter l’interface ITagger<T> . Cette interface affecte, à une mémoire tampon de texte donnée, des balises qui fournissent la mise en surbrillance du texte et d’autres effets visuels.
Pour implémenter un taggeur
Créez une classe qui implémente ITagger<T> un type
HighlightWordTag
et nommez-laHighlightWordTagger
.internal class HighlightWordTagger : ITagger<HighlightWordTag> { }
Ajoutez les champs et propriétés privés suivants à la classe :
Un ITextView, qui correspond à la vue de texte actuelle.
Qui ITextBuffercorrespond à la mémoire tampon de texte qui sous-tend l’affichage de texte.
Qui ITextSearchServiceest utilisé pour rechercher du texte.
Qui ITextStructureNavigatora des méthodes pour naviguer dans des étendues de texte.
A NormalizedSnapshotSpanCollection, qui contient l’ensemble de mots à mettre en surbrillance.
A SnapshotSpan, qui correspond au mot actuel.
A SnapshotPoint, qui correspond à la position actuelle du caret.
Objet de verrouillage.
ITextView View { get; set; } ITextBuffer SourceBuffer { get; set; } ITextSearchService TextSearchService { get; set; } ITextStructureNavigator TextStructureNavigator { get; set; } NormalizedSnapshotSpanCollection WordSpans { get; set; } SnapshotSpan? CurrentWord { get; set; } SnapshotPoint RequestedPoint { get; set; } object updateLock = new object();
Ajoutez un constructeur qui initialise les propriétés répertoriées précédemment et ajoute et PositionChanged des LayoutChanged gestionnaires d’événements.
public HighlightWordTagger(ITextView view, ITextBuffer sourceBuffer, ITextSearchService textSearchService, ITextStructureNavigator textStructureNavigator) { this.View = view; this.SourceBuffer = sourceBuffer; this.TextSearchService = textSearchService; this.TextStructureNavigator = textStructureNavigator; this.WordSpans = new NormalizedSnapshotSpanCollection(); this.CurrentWord = null; this.View.Caret.PositionChanged += CaretPositionChanged; this.View.LayoutChanged += ViewLayoutChanged; }
Les gestionnaires d’événements appellent la
UpdateAtCaretPosition
méthode.void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) { // If a new snapshot wasn't generated, then skip this layout if (e.NewSnapshot != e.OldSnapshot) { UpdateAtCaretPosition(View.Caret.Position); } } void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e) { UpdateAtCaretPosition(e.NewPosition); }
Vous devez également ajouter un
TagsChanged
événement appelé par la méthode de mise à jour.La
UpdateAtCaretPosition()
méthode recherche chaque mot dans la mémoire tampon de texte identique au mot où le curseur est positionné et construit une liste d’objets SnapshotSpan qui correspondent aux occurrences du mot. Il appelleSynchronousUpdate
ensuite , ce qui déclenche l’événementTagsChanged
.void UpdateAtCaretPosition(CaretPosition caretPosition) { SnapshotPoint? point = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity); if (!point.HasValue) return; // If the new caret position is still within the current word (and on the same snapshot), we don't need to check it if (CurrentWord.HasValue && CurrentWord.Value.Snapshot == View.TextSnapshot && point.Value >= CurrentWord.Value.Start && point.Value <= CurrentWord.Value.End) { return; } RequestedPoint = point.Value; UpdateWordAdornments(); } void UpdateWordAdornments() { SnapshotPoint currentRequest = RequestedPoint; List<SnapshotSpan> wordSpans = new List<SnapshotSpan>(); //Find all words in the buffer like the one the caret is on TextExtent word = TextStructureNavigator.GetExtentOfWord(currentRequest); bool foundWord = true; //If we've selected something not worth highlighting, we might have missed a "word" by a little bit if (!WordExtentIsValid(currentRequest, word)) { //Before we retry, make sure it is worthwhile if (word.Span.Start != currentRequest || currentRequest == currentRequest.GetContainingLine().Start || char.IsWhiteSpace((currentRequest - 1).GetChar())) { foundWord = false; } else { // Try again, one character previous. //If the caret is at the end of a word, pick up the word. word = TextStructureNavigator.GetExtentOfWord(currentRequest - 1); //If the word still isn't valid, we're done if (!WordExtentIsValid(currentRequest, word)) foundWord = false; } } if (!foundWord) { //If we couldn't find a word, clear out the existing markers SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(), null); return; } SnapshotSpan currentWord = word.Span; //If this is the current word, and the caret moved within a word, we're done. if (CurrentWord.HasValue && currentWord == CurrentWord) return; //Find the new spans FindData findData = new FindData(currentWord.GetText(), currentWord.Snapshot); findData.FindOptions = FindOptions.WholeWord | FindOptions.MatchCase; wordSpans.AddRange(TextSearchService.FindAll(findData)); //If another change hasn't happened, do a real update if (currentRequest == RequestedPoint) SynchronousUpdate(currentRequest, new NormalizedSnapshotSpanCollection(wordSpans), currentWord); } static bool WordExtentIsValid(SnapshotPoint currentRequest, TextExtent word) { return word.IsSignificant && currentRequest.Snapshot.GetText(word.Span).Any(c => char.IsLetter(c)); }
Effectue
SynchronousUpdate
une mise à jour synchrone sur lesWordSpans
propriétés etCurrentWord
déclenche l’événementTagsChanged
.void SynchronousUpdate(SnapshotPoint currentRequest, NormalizedSnapshotSpanCollection newSpans, SnapshotSpan? newCurrentWord) { lock (updateLock) { if (currentRequest != RequestedPoint) return; WordSpans = newSpans; CurrentWord = newCurrentWord; var tempEvent = TagsChanged; if (tempEvent != null) tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length))); } }
Vous devez implémenter la GetTags méthode. Cette méthode prend une collection d’objets SnapshotSpan et retourne une énumération d’étendues d’étiquettes.
En C#, implémentez cette méthode en tant qu’itérateur de rendement, ce qui active l’évaluation différée (autrement dit, l’évaluation de l’ensemble uniquement lorsque des éléments individuels sont accessibles) des balises. Dans Visual Basic, ajoutez les balises à une liste et retournez la liste.
Ici, la méthode retourne un TagSpan<T> objet qui a un « bleu », TextMarkerTagqui fournit un arrière-plan bleu.
public IEnumerable<ITagSpan<HighlightWordTag>> GetTags(NormalizedSnapshotSpanCollection spans) { if (CurrentWord == null) yield break; // Hold on to a "snapshot" of the word spans and current word, so that we maintain the same // collection throughout SnapshotSpan currentWord = CurrentWord.Value; NormalizedSnapshotSpanCollection wordSpans = WordSpans; if (spans.Count == 0 || wordSpans.Count == 0) yield break; // If the requested snapshot isn't the same as the one our words are on, translate our spans to the expected snapshot if (spans[0].Snapshot != wordSpans[0].Snapshot) { wordSpans = new NormalizedSnapshotSpanCollection( wordSpans.Select(span => span.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive))); currentWord = currentWord.TranslateTo(spans[0].Snapshot, SpanTrackingMode.EdgeExclusive); } // First, yield back the word the cursor is under (if it overlaps) // Note that we'll yield back the same word again in the wordspans collection; // the duplication here is expected. if (spans.OverlapsWith(new NormalizedSnapshotSpanCollection(currentWord))) yield return new TagSpan<HighlightWordTag>(currentWord, new HighlightWordTag()); // Second, yield all the other words in the file foreach (SnapshotSpan span in NormalizedSnapshotSpanCollection.Overlap(spans, wordSpans)) { yield return new TagSpan<HighlightWordTag>(span, new HighlightWordTag()); } }
Créer un fournisseur Tagger
Pour créer votre taggeur, vous devez implémenter un IViewTaggerProvider. Cette classe est une partie de composant MEF. Vous devez donc définir les attributs appropriés afin que cette extension soit reconnue.
Remarque
Pour plus d’informations sur MEF, consultez Managed Extensibility Framework (MEF).
Pour créer un fournisseur de balisage
Créez une classe nommée
HighlightWordTaggerProvider
qui implémente IViewTaggerProvider, puis exportez-la avec un ContentTypeAttribute « text » et un TagTypeAttribute de TextMarkerTag.[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class HighlightWordTaggerProvider : IViewTaggerProvider { }
Vous devez importer deux services d’éditeur, le ITextSearchService et le ITextStructureNavigatorSelectorService, pour instancier le balisage.
[Import] internal ITextSearchService TextSearchService { get; set; } [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
Implémentez la CreateTagger méthode pour retourner une instance de
HighlightWordTagger
.public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag { //provide highlighting only on the top buffer if (textView.TextBuffer != buffer) return null; ITextStructureNavigator textStructureNavigator = TextStructureNavigatorSelector.GetTextStructureNavigator(buffer); return new HighlightWordTagger(textView, buffer, TextSearchService, textStructureNavigator) as ITagger<T>; }
Générer et tester le code
Pour tester ce code, générez la solution HighlightWordTest et exécutez-la dans l’instance expérimentale.
Pour générer et tester la solution HighlightWordTest
Générez la solution.
Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est démarrée.
Créez un fichier texte et tapez du texte dans lequel les mots sont répétés, par exemple « hello hello hello ».
Positionnez le curseur dans l’une des occurrences de « hello ». Chaque occurrence doit être mise en surbrillance en bleu.