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

  1. 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.

  2. Fügen Sie dem Projekt eine Elementvorlage für Editorklassifizierer hinzu. Weitere Informationen finden Sie unter Erstellen einer Erweiterung mit einer Editorelementvorlage.

  3. 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

  1. Fügen Sie eine Klassendatei hinzu, und nennen Sie sie "HighlightWordTag".

  2. Fügen Sie die folgenden Verweise hinzu:

    1. Microsoft.VisualStudio.CoreUtility

    2. Microsoft.VisualStudio.Text.Data

    3. Microsoft.VisualStudio.Text.Logic

    4. Microsoft.VisualStudio.Text.UI

    5. Microsoft.VisualStudio.Text.UI.Wpf

    6. System.ComponentModel.Composition

    7. Presentation.Core

    8. Presentation.Framework

  3. 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;
    
  4. Erstellen Sie eine Klasse, von TextMarkerTag der sie erbt, und nennen Sie sie HighlightWordTag.

    internal class HighlightWordTag : TextMarkerTag
    {
    
    }
    
  5. 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
    {
    
    }
    
  6. 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;
    }
    
  7. Ü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

  1. Erstellen Sie eine Klasse, die den Typ HighlightWordTagimplementiertITagger<T>, und nennen Sie sieHighlightWordTagger.

    internal class HighlightWordTagger : ITagger<HighlightWordTag>
    {
    
    }
    
  2. Fügen Sie der Klasse die folgenden privaten Felder und Eigenschaften hinzu:

    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();
    
    
  3. 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;
    }
    
    
  4. 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);
    }
    
  5. Sie müssen auch ein TagsChanged Ereignis hinzufügen, das von der Updatemethode aufgerufen wird.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  6. 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 aufgerufen SynchronousUpdate, wodurch das TagsChanged 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));
    }
    
    
  7. Dies SynchronousUpdate führt eine synchrone Aktualisierung der WordSpans Ereignisse und CurrentWord Eigenschaften durch und löst das TagsChanged 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)));
        }
    }
    
  8. 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

  1. 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
    { }
    
  2. 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; }
    
    
  3. 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

  1. Erstellen Sie die Projektmappe.

  2. Wenn Sie dieses Projekt im Debugger ausführen, wird eine zweite Instanz von Visual Studio gestartet.

  3. Erstellen Sie eine Textdatei, und geben Sie Text ein, in dem die Wörter wiederholt werden, z. B. "Hello Hello Hello".

  4. Positionieren Sie den Cursor in einem der Vorkommen von "hello". Jedes Vorkommen sollte blau hervorgehoben werden.