Procédure pas - à - pas : Mettre le texte en surbrillance
Vous pouvez ajouter des effets visuels dans l'éditeur en créant des éléments managées (MEF) managed extensibility framework. Cette procédure pas - à - pas indique comment mettre en surbrillance chaque occurrence du mot actuel dans un fichier texte. Si un mot se produit plusieurs fois dans un fichier texte, et vous positionnez le signe insertion dans une occurrence, chaque occurrence est mise en surbrillance.
Composants requis
Pour exécuter cette procédure, vous devez installer Kit de développement logiciel Visual Studio 2010.
Notes
Pour plus d'informations sur le kit de développement Visual Studio, consultez Étendre la présentation de Visual Studio.Pour savoir comment télécharger le kit de développement Visual Studio, consultez Visual Studio Extensibility Developer Center sur le site Web MSDN.
Créer un projet MEF
Pour créer un projet MEF
Créez un projet de classifieur d'éditeur. nommez la solution HighlightWordTest.
Ouvrez le fichier source.extension.vsixmanifest dans l'éditeur de manifeste VSIX.
Assurez -vous que le titre d' Content contient un type de contenu composant MEF et qu' Path est défini à HighlightWordTest.dll.
Enregistrez et fermez le fichier source.extension.vsixmanifest.
supprimez les fichiers de classe existants.
définir un TextMarkerTag
La première étape de mise en surbrillance du texte est sous-classer TextMarkerTag et définit son apparence.
pour définir un TextMarkerTag et un MarkerFormatDefinition
ajoutez un fichier de classe et nommez-le HighlightWordTag.
importez les espaces de noms suivants.
Imports System Imports System.Collections.Generic Imports System.ComponentModel.Composition Imports System.Linq Imports System.Threading Imports System.Windows.Media Imports Microsoft.VisualStudio.Text Imports Microsoft.VisualStudio.Text.Classification Imports Microsoft.VisualStudio.Text.Editor Imports Microsoft.VisualStudio.Text.Operations Imports Microsoft.VisualStudio.Text.Tagging Imports Microsoft.VisualStudio.Utilities
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Threading; using System.Windows.Media; 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;
Créez une classe qui hérite d' TextMarkerTag et nommez HighlightWordTag.
Friend Class HighlightWordTag Inherits TextMarkerTag
internal class HighlightWordTag : TextMarkerTag
Créez une deuxième classe qui hérite d' MarkerFormatDefinition, et nommez HighlightWordFormatDefinition. Pour utiliser cette définition de format pour votre balise, vous devez les exporter avec les attributs suivants :
NameAttribute: les balises utilisent cette opération pour référencer ce format
UserVisibleAttribute: cela entraîne l'affichage au format de l'interface utilisateur
<Export(GetType(EditorFormatDefinition))> <Name("MarkerFormatDefinition/HighlightWordFormatDefinition")> <UserVisible(True)> Friend Class HighlightWordFormatDefinition Inherits MarkerFormatDefinition
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition
Dans le constructeur pour HighlightWordFormatDefinition, affectez à son nom complet et apparence. La propriété d'arrière-plan définit la couleur de remplissage, tandis que la propriété de premier plan définit la couleur de la bordure.
Public Sub New() Me.BackgroundColor = Colors.LightBlue Me.ForegroundColor = Colors.DarkBlue Me.DisplayName = "Highlight Word" Me.ZOrder = 5 End Sub
public HighlightWordFormatDefinition() { this.BackgroundColor = Colors.LightBlue; this.ForegroundColor = Colors.DarkBlue; this.DisplayName = "Highlight Word"; this.ZOrder = 5; }
Dans le constructeur pour HighlightWordTag, passez le nom de la définition de format que vous venez de créer.
Public Sub New() MyBase.New("MarkerFormatDefinition/HighlightWordFormatDefinition") End Sub
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
implémenter un ITagger
L'étape suivante consiste à implémenter l'interface d' ITagger . Cette interface assigne, dans une mémoire tampon de texte donnée, les balises qui fournissent le texte mise en surbrillance et à d'autres effets visuels.
Pour implémenter un balises
Créez une classe qui implémente ITagger de type HighlightWordTag, et nommez HighlightWordTagger.
Friend Class HighlightWordTagger Implements ITagger(Of HighlightWordTag)
internal class HighlightWordTagger : ITagger<HighlightWordTag>
Ajoutez les propriétés et les champs privés suivants à la classe :
ITextView, qui correspond à l'affichage de texte actuel.
ITextBuffer, qui correspond à la mémoire tampon de texte à la base de l'affichage de texte.
ITextSearchService, qui est utilisé pour rechercher du texte.
ITextStructureNavigator, qui a des méthodes pour naviguer au sein de les étendues de texte.
NormalizedSnapshotSpanCollection, qui contient l'ensemble de mots pour mettre en surbrillance.
SnapshotSpan, qui correspond au mot actuel.
SnapshotPoint, qui correspond à la position actuelle du signe insertion.
Un objet de verrouillage.
Private _View As ITextView Private Property View() As ITextView Get Return _View End Get Set(ByVal value As ITextView) _View = value End Set End Property Private _SourceBuffer As ITextBuffer Private Property SourceBuffer() As ITextBuffer Get Return _SourceBuffer End Get Set(ByVal value As ITextBuffer) _SourceBuffer = value End Set End Property Private _TextSearchService As ITextSearchService Private Property TextSearchService() As ITextSearchService Get Return _TextSearchService End Get Set(ByVal value As ITextSearchService) _TextSearchService = value End Set End Property Private _TextStructureNavigator As ITextStructureNavigator Private Property TextStructureNavigator() As ITextStructureNavigator Get Return _TextStructureNavigator End Get Set(ByVal value As ITextStructureNavigator) _TextStructureNavigator = value End Set End Property Private _WordSpans As NormalizedSnapshotSpanCollection Private Property WordSpans() As NormalizedSnapshotSpanCollection Get Return _WordSpans End Get Set(ByVal value As NormalizedSnapshotSpanCollection) _WordSpans = value End Set End Property Private _CurrentWord As System.Nullable(Of SnapshotSpan) Private Property CurrentWord() As System.Nullable(Of SnapshotSpan) Get Return _CurrentWord End Get Set(ByVal value As System.Nullable(Of SnapshotSpan)) _CurrentWord = value End Set End Property Private _RequestedPoint As SnapshotPoint Private Property RequestedPoint() As SnapshotPoint Get Return _RequestedPoint End Get Set(ByVal value As SnapshotPoint) _RequestedPoint = value End Set End Property Private updateLock As New Object()
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 LayoutChanged et des gestionnaires d'événements d' PositionChanged .
Public Sub New(ByVal view As ITextView, ByVal sourceBuffer As ITextBuffer, ByVal textSearchService As ITextSearchService, ByVal textStructureNavigator As ITextStructureNavigator) Me.View = view Me.SourceBuffer = sourceBuffer Me.TextSearchService = textSearchService Me.TextStructureNavigator = textStructureNavigator Me.WordSpans = New NormalizedSnapshotSpanCollection() Me.CurrentWord = Nothing AddHandler Me.View.Caret.PositionChanged, AddressOf CaretPositionChanged AddHandler Me.View.LayoutChanged, AddressOf ViewLayoutChanged End Sub
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 les deux appelle la méthode d' UpdateAtCaretPosition .
Private Sub ViewLayoutChanged(ByVal sender As Object, ByVal e As TextViewLayoutChangedEventArgs) ' If a new snapshot wasn't generated, then skip this layout If e.NewSnapshot IsNot e.OldSnapshot Then UpdateAtCaretPosition(View.Caret.Position) End If End Sub Private Sub CaretPositionChanged(ByVal sender As Object, ByVal e As CaretPositionChangedEventArgs) UpdateAtCaretPosition(e.NewPosition) End Sub
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 événement d' TagsChanged qui sera appelée par la méthode de mise à jour.
Public Event TagsChanged(ByVal sender As Object, ByVal e As SnapshotSpanEventArgs) _ Implements ITagger(Of HighlightWordTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
La méthode d' UpdateAtCaretPosition() recherche chaque mot dans la mémoire tampon de texte qui est identique au mot dans lequel le curseur est positionné et construit une liste d'objets d' SnapshotSpan qui correspondent aux occurrences du mot. Il appelle ensuite SynchronousUpdate, qui déclenche l'événement d' TagsChanged .
Private Sub UpdateAtCaretPosition(ByVal caretPosition As CaretPosition) Dim point As System.Nullable(Of SnapshotPoint) = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity) If Not point.HasValue Then Exit Sub End If ' 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 AndAlso CurrentWord.Value.Snapshot Is View.TextSnapshot AndAlso point.Value > CurrentWord.Value.Start AndAlso point.Value < CurrentWord.Value.[End] Then Exit Sub End If RequestedPoint = point.Value UpdateWordAdornments() End Sub Private Sub UpdateWordAdornments() Dim currentRequest As SnapshotPoint = RequestedPoint Dim wordSpans As New List(Of SnapshotSpan)() 'Find all words in the buffer like the one the caret is on Dim word As TextExtent = TextStructureNavigator.GetExtentOfWord(currentRequest) Dim foundWord As Boolean = True 'If we've selected something not worth highlighting, we might have missed a "word" by a little bit If Not WordExtentIsValid(currentRequest, word) Then 'Before we retry, make sure it is worthwhile If word.Span.Start <> currentRequest OrElse currentRequest = currentRequest.GetContainingLine().Start OrElse Char.IsWhiteSpace((currentRequest - 1).GetChar()) Then 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 Not WordExtentIsValid(currentRequest, word) Then foundWord = False End If End If End If If Not foundWord Then 'If we couldn't find a word, clear out the existing markers SynchronousUpdate(currentRequest, New NormalizedSnapshotSpanCollection(), Nothing) Exit Sub End If Dim currentWord__1 As SnapshotSpan = word.Span 'If this is the current word, and the caret moved within a word, we're done. If CurrentWord.HasValue AndAlso currentWord__1 = CurrentWord Then Exit Sub End If 'Find the new spans Dim findData As New FindData(currentWord__1.GetText(), currentWord__1.Snapshot) findData.FindOptions = FindOptions.WholeWord Or FindOptions.MatchCase wordSpans.AddRange(TextSearchService.FindAll(findData)) 'If another change hasn't happened, do a real update If currentRequest = RequestedPoint Then SynchronousUpdate(currentRequest, New NormalizedSnapshotSpanCollection(wordSpans), currentWord__1) End If End Sub Private Shared Function WordExtentIsValid(ByVal currentRequest As SnapshotPoint, ByVal word As TextExtent) As Boolean Return word.IsSignificant AndAlso currentRequest.Snapshot.GetText(word.Span).Any(Function(c) Char.IsLetter(c)) End Function
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)); }
SynchronousUpdate exécute une mise à jour synchrones sur les propriétés d' WordSpans et d' CurrentWord , et déclenche l'événement d' TagsChanged .
Private Sub SynchronousUpdate(ByVal currentRequest As SnapshotPoint, ByVal newSpans As NormalizedSnapshotSpanCollection, ByVal newCurrentWord As System.Nullable(Of SnapshotSpan)) SyncLock updateLock If currentRequest <> RequestedPoint Then Exit Sub End If WordSpans = newSpans CurrentWord = newCurrentWord RaiseEvent TagsChanged(Me, New SnapshotSpanEventArgs(New SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length))) End SyncLock End Sub
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 méthode d' GetTags . Cette méthode prend une collection d'objets SnapshotSpan et retourne une énumération des étendues de balises.
En c#, appliquez cette méthode comme itérateur de rendement, qui permet l'évaluation tardive (autrement dit, évaluation de l'ensemble uniquement lorsque des éléments sont accessibles) des balises. En Visual Basic, ajoutez des balises d'une liste et retourne la liste.
Ici la méthode retourne un objet d' TagSpan qui a TextMarkerTag« bleu », qui fournit un arrière-plan bleu.
Public Function GetTags(ByVal spans As NormalizedSnapshotSpanCollection) As IEnumerable(Of ITagSpan(Of HighlightWordTag)) Implements ITagger(Of HighlightWordTag).GetTags If CurrentWord Is Nothing Then Return Nothing Exit Function End If ' Hold on to a "snapshot" of the word spans and current word, so that we maintain the same ' collection throughout Dim currentWord__1 As SnapshotSpan = CurrentWord.Value Dim wordSpans__2 As NormalizedSnapshotSpanCollection = WordSpans If spans.Count = 0 OrElse WordSpans.Count = 0 Then Return Nothing Exit Function End If ' 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 IsNot wordSpans__2(0).Snapshot Then wordSpans__2 = New NormalizedSnapshotSpanCollection(wordSpans__2.[Select](Function(span) span.TranslateTo(spans(0).Snapshot, SpanTrackingMode.EdgeExclusive))) currentWord__1 = currentWord__1.TranslateTo(spans(0).Snapshot, SpanTrackingMode.EdgeExclusive) End If 'in order to emulate the C# yield return functionality, 'create a list and add all the relevant spans to it, then return the list Dim list As List(Of TagSpan(Of HighlightWordTag)) list = New List(Of TagSpan(Of HighlightWordTag))() If spans.OverlapsWith(New NormalizedSnapshotSpanCollection(currentWord__1)) Then list.Add(New TagSpan(Of HighlightWordTag)(CurrentWord, New HighlightWordTag())) End If For Each span As SnapshotSpan In NormalizedSnapshotSpanCollection.Overlap(spans, wordSpans__2) list.Add(New TagSpan(Of HighlightWordTag)(span, New HighlightWordTag())) Next Return List End Function
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 de balises
Pour créer votre balises, vous devez implémenter IViewTaggerProvider. Cette classe est un composant MEF, vous devez donc définir les attributs corrects afin que cette extension soit reconnue.
Notes
Pour plus d'informations sur MEF, consultez Managed Extensibility Framework (MEF).
Pour créer un fournisseur de balises
Créez une classe nommée HighlightWordTaggerProvider qui implémente IViewTaggerProvider, et exportez-la avec ContentTypeAttribute « texte » et TagTypeAttribute d' TextMarkerTag.
<Export(GetType(IViewTaggerProvider))> <ContentType("text")> <TagType(GetType(TextMarkerTag))> Friend Class HighlightWordTaggerProvider Implements IViewTaggerProvider
[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class HighlightWordTaggerProvider : IViewTaggerProvider
Vous devez importer deux services d'éditeur, ITextSearchService et ITextStructureNavigatorSelectorService, pour instancier le balises.
Private _TextSearchService As ITextSearchService <Import()> _ Friend Property TextSearchService() As ITextSearchService Get Return _TextSearchService End Get Set(ByVal value As ITextSearchService) _TextSearchService = value End Set End Property Private _TextStructureNavigatorSelector As ITextStructureNavigatorSelectorService <Import()> Friend Property TextStructureNavigatorSelector() As ITextStructureNavigatorSelectorService Get Return _TextStructureNavigatorSelector End Get Set(ByVal value As ITextStructureNavigatorSelectorService) _TextStructureNavigatorSelector = value End Set End Property
[Import] internal ITextSearchService TextSearchService { get; set; } [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
Implémentez la méthode d' CreateTagger``1 pour retourner une instance d' HighlightWordTagger.
Public Function CreateTagger(Of T As ITag)(ByVal textView As ITextView, ByVal buffer As ITextBuffer) As ITagger(Of T) Implements IViewTaggerProvider.CreateTagger 'provide highlighting only on the top buffer If textView.TextBuffer IsNot buffer Then Return Nothing End If Dim textStructureNavigator As ITextStructureNavigator = TextStructureNavigatorSelector.GetTextStructureNavigator(buffer) Return TryCast(New HighlightWordTagger(textView, buffer, TextSearchService, textStructureNavigator), ITagger(Of T)) End Function
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ération et test de code
Pour tester ce code, générez la solution de HighlightWordTest et exécutez -la dans l'instance expérimentale.
Pour générer et tester la solution de HighlightWordTest
Générez la solution.
Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est instancié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.
Voir aussi
Tâches
Procédure pas - à - pas : lier un type de contenu à une extension de nom de fichier