Exemplarische Vorgehensweise: Hervorheben von Text
Sie können dem Editor verschiedene visuelle Effekte hinzufügen, indem Sie Komponententeile von Managed Extensibility Framework (MEF) erstellen. In dieser exemplarischen Vorgehensweise wird gezeigt, wie Sie jedes Vorkommen des aktuellen Worts in einer Textdatei hervorheben. Wenn ein Wort in einer Textdatei mehrmals auftritt und Sie das Caret in einem Vorkommen positionieren, wird jedes Vorkommen hervorgehoben.
Erstellen eines MEF-Projekts
Erstellen Sie ein C#VSIX-Projekt. (Im Dialogfeld "Neues Projekt ", wählen Sie Visual C# / Erweiterbarkeit und dann VSIX-Projekt aus.) Benennen Sie die Lösung
HighlightWordTest
.Fügen Sie dem Projekt eine Elementvorlage für Editorklassifizierer hinzu. Weitere Informationen finden Sie unter Erstellen einer Erweiterung mit einer Editorelementvorlage.
Löschen Sie die vorhandenen Klassendateien.
Definieren eines TextMarkerTags
Der erste Schritt beim Hervorheben von Text besteht darin, Unterklassen TextMarkerTag zu definieren und seine Darstellung zu definieren.
So definieren Sie ein TextMarkerTag und eine MarkerFormatDefinition
Fügen Sie eine Klassendatei hinzu, und nennen Sie sie "HighlightWordTag".
Fügen Sie die folgenden Verweise hinzu:
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
Importieren Sie die folgenden Namespaces.
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;
Erstellen Sie eine Klasse, von TextMarkerTag der sie erbt, und nennen Sie sie
HighlightWordTag
.internal class HighlightWordTag : TextMarkerTag { }
Erstellen Sie eine zweite Klasse, die von MarkerFormatDefinition, und nennen Sie sie
HighlightWordFormatDefinition
. Um diese Formatdefinition für Ihr Tag zu verwenden, müssen Sie sie mit den folgenden Attributen exportieren:NameAttribute: Tags verwenden dieses Format, um auf dieses Format zu verweisen.
UserVisibleAttribute: Dies bewirkt, dass das Format in der Benutzeroberfläche angezeigt wird.
[Export(typeof(EditorFormatDefinition))] [Name("MarkerFormatDefinition/HighlightWordFormatDefinition")] [UserVisible(true)] internal class HighlightWordFormatDefinition : MarkerFormatDefinition { }
Definieren Sie im Konstruktor für HighlightWordFormatDefinition den Anzeigenamen und die Darstellung. Die Background-Eigenschaft definiert die Füllfarbe, während die Foreground-Eigenschaft die Rahmenfarbe definiert.
public HighlightWordFormatDefinition() { this.BackgroundColor = Colors.LightBlue; this.ForegroundColor = Colors.DarkBlue; this.DisplayName = "Highlight Word"; this.ZOrder = 5; }
Übergeben Sie im Konstruktor für HighlightWordTag den Namen der von Ihnen erstellten Formatdefinition.
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
Implementieren eines ITaggers
Der nächste Schritt besteht darin, die ITagger<T> Schnittstelle zu implementieren. Diese Schnittstelle weist einem bestimmten Textpuffer Tags zu, die Textmarkierungen und andere visuelle Effekte bereitstellen.
So implementieren Sie einen Tagger
Erstellen Sie eine Klasse, die den Typ
HighlightWordTag
implementiertITagger<T>, und nennen Sie sieHighlightWordTagger
.internal class HighlightWordTagger : ITagger<HighlightWordTag> { }
Fügen Sie der Klasse die folgenden privaten Felder und Eigenschaften hinzu:
Ein ITextView, der der aktuellen Textansicht entspricht.
Ein ITextBuffer, der dem Textpuffer entspricht, der der Textansicht zugrunde liegt.
Ein ITextSearchService, der zum Suchen von Text verwendet wird.
Ein ITextStructureNavigator, der Methoden zum Navigieren innerhalb von Textabschnitten enthält.
A NormalizedSnapshotSpanCollection, das die Gruppe der hervorzuhebenden Wörter enthält.
A SnapshotSpan, das dem aktuellen Wort entspricht.
A SnapshotPoint, das der aktuellen Position des Carets entspricht.
Ein Sperrobjekt.
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();
Fügen Sie einen Konstruktor hinzu, der die zuvor aufgeführten Eigenschaften initialisiert und Ereignishandler hinzufügt und PositionChanged Ereignishandler hinzufügtLayoutChanged.
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; }
Die Ereignishandler rufen beide die
UpdateAtCaretPosition
Methode auf.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); }
Sie müssen auch ein
TagsChanged
Ereignis hinzufügen, das von der Updatemethode aufgerufen wird.Die
UpdateAtCaretPosition()
Methode findet jedes Wort im Textpuffer, das mit dem Wort identisch ist, in dem der Cursor positioniert ist, und erstellt eine Liste von SnapshotSpan Objekten, die den Vorkommen des Worts entsprechen. Anschließend wird das Ereignis aufgerufenSynchronousUpdate
, wodurch dasTagsChanged
Ereignis ausgelöst wird.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)); }
Dies
SynchronousUpdate
führt eine synchrone Aktualisierung derWordSpans
Ereignisse undCurrentWord
Eigenschaften durch und löst dasTagsChanged
Ereignis aus.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))); } }
Sie müssen die GetTags Methode implementieren. Diese Methode verwendet eine Auflistung von SnapshotSpan Objekten und gibt eine Enumeration von Tag-Spans zurück.
Implementieren Sie in C# diese Methode als Ertrags-Iterator, der eine faule Auswertung (d. h. die Auswertung des Satzes nur dann ermöglicht, wenn auf einzelne Elemente zugegriffen wird) der Tags. Fügen Sie in Visual Basic die Tags zu einer Liste hinzu, und geben Sie die Liste zurück.
Hier gibt die Methode ein TagSpan<T> Objekt zurück, das einen blauen TextMarkerTagHintergrund aufweist.
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()); } }
Erstellen eines Tagger-Anbieters
Um Ihren Tagger zu erstellen, müssen Sie eine IViewTaggerProvider. Diese Klasse ist ein MEF-Komponententeil, daher müssen Sie die richtigen Attribute so festlegen, dass diese Erweiterung erkannt wird.
Hinweis
Weitere Informationen zu MEF finden Sie unter Managed Extensibility Framework (MEF).
So erstellen Sie einen Taggeranbieter
Erstellen Sie eine Klasse mit dem Namen
HighlightWordTaggerProvider
, die sie implementiert IViewTaggerProvider, und exportieren Sie sie mit einem ContentTypeAttribute "Text" und einem TagTypeAttribute von TextMarkerTag.[Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(TextMarkerTag))] internal class HighlightWordTaggerProvider : IViewTaggerProvider { }
Sie müssen zwei Editordienste importieren, die ITextSearchService und die ITextStructureNavigatorSelectorService, um den Tagger zu instanziieren.
[Import] internal ITextSearchService TextSearchService { get; set; } [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }
Implementieren Sie die CreateTagger Methode, um eine Instanz von
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>; }
Erstellen und Testen des Codes
Um diesen Code zu testen, erstellen Sie die HighlightWordTest-Lösung, und führen Sie sie in der experimentellen Instanz aus.
So erstellen und testen Sie die HighlightWordTest-Lösung
Erstellen Sie die Projektmappe.
Wenn Sie dieses Projekt im Debugger ausführen, wird eine zweite Instanz von Visual Studio gestartet.
Erstellen Sie eine Textdatei, und geben Sie Text ein, in dem die Wörter wiederholt werden, z. B. "Hello Hello Hello".
Positionieren Sie den Cursor in einem der Vorkommen von "hello". Jedes Vorkommen sollte blau hervorgehoben werden.