Procedura dettagliata: Evidenziare il testo
È possibile aggiungere diversi effetti visivi all'editor creando parti del componente MEF (Managed Extensibility Framework). Questa procedura dettagliata illustra come evidenziare ogni occorrenza della parola corrente in un file di testo. Se una parola si verifica più di una volta in un file di testo e si posiziona il cursore in un'occorrenza, ogni occorrenza viene evidenziata.
Creare un progetto MEF
Creare un progetto VSIX C#. (In Finestra di dialogo Nuovo progetto , selezionare Visual C# / Estendibilità e quindi progetto VSIX. Assegnare alla soluzione
HighlightWordTest
il nome .Aggiungere un modello di elemento classificatore dell'editor al progetto. Per altre informazioni, vedere Creare un'estensione con un modello di elemento dell'editor.
Eliminare i file di classe esistenti.
Definire un textMarkerTag
Il primo passaggio nell'evidenziazione del testo consiste nel sottoclasse TextMarkerTag e definirne l'aspetto.
Per definire un TextMarkerTag e un MarkerFormatDefinition
Aggiungere un file di classe e denominarlo HighlightWordTag.
Aggiungere i riferimenti seguenti:
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
Importare gli spazi dei nomi seguenti.
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;
Creare una classe che eredita da TextMarkerTag e denominarla
HighlightWordTag
.internal class HighlightWordTag : TextMarkerTag { }
Creare una seconda classe che eredita da MarkerFormatDefinitione denominarla
HighlightWordFormatDefinition
. Per usare questa definizione di formato per il tag, è necessario esportarla con gli attributi seguenti:NameAttribute: i tag usano questa opzione per fare riferimento a questo formato
UserVisibleAttribute: in questo modo il formato viene visualizzato nell'interfaccia utente
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition { }
Nel costruttore di HighlightWordFormatDefinition definirne il nome visualizzato e l'aspetto. La proprietà Background definisce il colore di riempimento, mentre la proprietà Foreground definisce il colore del bordo.
public HighlightWordFormatDefinition() { this.BackgroundColor = Colors.LightBlue; this.ForegroundColor = Colors.DarkBlue; this.DisplayName = "Highlight Word"; this.ZOrder = 5; }
Nel costruttore di HighlightWordTag passare il nome della definizione di formato creata.
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
Implementare un ITagger
Il passaggio successivo consiste nell'implementare l'interfaccia ITagger<T> . Questa interfaccia assegna, a un determinato buffer di testo, tag che forniscono l'evidenziazione del testo e altri effetti visivi.
Per implementare un tagger
Creare una classe che implementa ITagger<T> il tipo
HighlightWordTag
e denominarlaHighlightWordTagger
.internal class HighlightWordTagger : ITagger<HighlightWordTag> { }
Aggiungere i campi privati e le proprietà seguenti alla classe :
Oggetto ITextView, che corrisponde alla visualizzazione testo corrente.
Oggetto ITextBuffer, che corrisponde al buffer di testo sottostante la visualizzazione testo.
Oggetto ITextSearchService, utilizzato per trovare il testo.
Oggetto ITextStructureNavigator, che dispone di metodi per spostarsi all'interno di intervalli di testo.
Oggetto NormalizedSnapshotSpanCollection, che contiene il set di parole da evidenziare.
Oggetto SnapshotSpan, che corrisponde alla parola corrente.
Oggetto SnapshotPoint, che corrisponde alla posizione corrente del cursore.
Oggetto lock.
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();
Aggiungere un costruttore che inizializza le proprietà elencate in precedenza e aggiunge LayoutChanged i gestori eventi e PositionChanged .
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; }
I gestori eventi chiamano entrambi il
UpdateAtCaretPosition
metodo .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); }
È inoltre necessario aggiungere un
TagsChanged
evento chiamato dal metodo update.Il
UpdateAtCaretPosition()
metodo trova ogni parola nel buffer di testo identica alla parola in cui è posizionato il cursore e costruisce un elenco di SnapshotSpan oggetti che corrispondono alle occorrenze della parola. Chiama quindiSynchronousUpdate
, che genera l'eventoTagsChanged
.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
esegue un aggiornamento sincrono sulleWordSpans
proprietà eCurrentWord
e genera l'eventoTagsChanged
.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))); } }
È necessario implementare il GetTags metodo . Questo metodo accetta una raccolta di SnapshotSpan oggetti e restituisce un'enumerazione di intervalli di tag.
In C# implementare questo metodo come iteratore di rendimento, che consente la valutazione differita (ovvero la valutazione del set solo quando si accede a singoli elementi) dei tag. In Visual Basic aggiungere i tag a un elenco e restituire l'elenco.
In questo caso il metodo restituisce un TagSpan<T> oggetto con un "blu" TextMarkerTag, che fornisce uno sfondo blu.
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()); } }
Creare un provider tagger
Per creare il tagger, è necessario implementare un oggetto IViewTaggerProvider. Questa classe è una parte del componente MEF, pertanto è necessario impostare gli attributi corretti in modo che questa estensione venga riconosciuta.
Nota
Per altre informazioni su MEF, vedere Managed Extensibility Framework (MEF).
Per creare un provider di tagger
Creare una classe denominata
HighlightWordTaggerProvider
che implementa IViewTaggerProvidered esportarla con un ContentTypeAttribute oggetto "text" e un TagTypeAttribute di TextMarkerTag.[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class HighlightWordTaggerProvider : IViewTaggerProvider { }
È necessario importare due servizi dell'editor ITextSearchService ITextStructureNavigatorSelectorService, e , per creare un'istanza del tagger.
[Import] internal ITextSearchService TextSearchService { get; set; } [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
Implementare il CreateTagger metodo per restituire un'istanza di
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>; }
Compilare e testare il codice
Per testare questo codice, compilare la soluzione HighlightWordTest ed eseguirla nell'istanza sperimentale.
Per compilare e testare la soluzione HighlightWordTest
Compilare la soluzione.
Quando si esegue questo progetto nel debugger, viene avviata una seconda istanza di Visual Studio.
Creare un file di testo e digitare un testo in cui le parole vengono ripetute, ad esempio "hello hello hello".
Posizionare il cursore in una delle occorrenze di "hello". Ogni occorrenza deve essere evidenziata in blu.