procedura dettagliata: Evidenziazione del testo
È possibile aggiungere effetti visivi diversi editor tramite gli elementi del Framework (MEF) di estensibilità gestito. In questa procedura dettagliata viene illustrato come evidenziare tutte le occorrenze di una parola corrente in un file di testo. Se una parola si verifica più volte in un file di testo e inserire il cursore in una occorrenza, ogni occorrenza viene evidenziata.
Prerequisiti
Per completare questa procedura dettagliata, è necessario installare Visual Studio 2010 SDK.
Nota
per ulteriori informazioni su Visual Studio SDK, vedere Cenni preliminari sull'estensione di Visual Studio.Per ulteriori informazioni su come scaricare Visual Studio SDK, vedere Centro per sviluppatori di estensibilità di Visual Studio il sito Web MSDN.
Creare un progetto MEF
Per creare un progetto MEF
Creare un progetto di classificatore editor. Assegnare alla soluzione HighlightWordTest.
Aprire il file source.extension.vsixmanifest nell'editor del manifesto VSIX.
Assicurarsi che la direzione di Content contenga un tipo di contenuto componente MEF e che Path è impostato su HighlightWordTest.dll.
salvare e chiudere source.extension.vsixmanifest.
Eliminare i file di classe esistenti.
Definizione di TextMarkerTag
Il primo passaggio di testo di evidenziazione è la sottoclasse TextMarkerTag e definisce l'aspetto.
Per definire un TextMarkerTag e un MarkerFormatDefinition
Aggiungere il file di classe e denominarlo HighlightWordTag.
Includere gli spazi dei nomi seguenti.
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;
Creare una classe che eredita da TextMarkerTag e denominarla HighlightWordTag.
Friend Class HighlightWordTag Inherits TextMarkerTag
internal class HighlightWordTag : TextMarkerTag
Creare un'altra classe che eredita da MarkerFormatDefinitione denominarla HighlightWordFormatDefinition. Per utilizzare questa definizione di formato per il tag, è necessario esportarlo con gli attributi seguenti:
NameAttribute: i tag utilizzano questo per fare riferimento a questo formato
UserVisibleAttribute: in questo modo il formato a visualizzato nell'interfaccia utente
<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
Nel costruttore per HighlightWordFormatDefinition, definirne il nome visualizzato e l'aspetto. La proprietà background definisce il colore di riempimento, mentre la proprietà del primo piano definisce il colore del bordo.
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; }
Nel costruttore per HighlightWordTag, passare il nome della definizione di formato appena creata.
Public Sub New() MyBase.New("MarkerFormatDefinition/HighlightWordFormatDefinition") End Sub
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
implementare un ITagger
Il passaggio successivo consiste nell'implementazione di ITagger . Questa interfaccia, assegna a un buffer di testo specificato, i tag che forniscono l'evidenziazione di testo e altri effetti visivi.
Per implementare un tagger
Creare una classe che implementa ITagger di tipo HighlightWordTage denominarla HighlightWordTagger.
Friend Class HighlightWordTagger Implements ITagger(Of HighlightWordTag)
internal class HighlightWordTagger : ITagger<HighlightWordTag>
Aggiungere i seguenti campi e proprietà privati nella classe:
ITextView, che corrisponde alla visualizzazione di testo corrente.
ITextBuffer, che corrisponde al buffer di testo sottostante della visualizzazione di testo.
ITextSearchService, utilizzata per cercare il testo.
ITextStructureNavigator, che include metodi per spostarsi all'interno di intervalli di testo.
NormalizedSnapshotSpanCollection, che contiene il set di parole per evidenziare.
SnapshotSpan, che corrisponde alla parola corrente.
SnapshotPoint, che corrisponde alla posizione corrente del cursore.
Un oggetto del blocco.
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();
Aggiungere un costruttore per inizializzare le proprietà elencate in precedenza e aggiunge LayoutChanged e i gestori eventi di 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; }
I gestori eventi sia chiamano il metodo di 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); }
È necessario aggiungere anche un evento di TagsChanged che verrà chiamato dal metodo update.
Public Event TagsChanged(ByVal sender As Object, ByVal e As SnapshotSpanEventArgs) _ Implements ITagger(Of HighlightWordTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
Il metodo di UpdateAtCaretPosition() cerca ogni parola nel buffer di testo identica alla parola in cui il cursore viene posizionato e viene costruito un elenco con oggetti di SnapshotSpan che corrispondono alle occorrenze di una parola. Chiama quindi SynchronousUpdate, che genera l'evento di 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)); }
The SynchronousUpdate performs a synchronous update on the WordSpans and CurrentWord properties, and raises the TagsChanged event.
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))); } }
È necessario implementare il metodo di GetTags . Questo metodo accetta una raccolta di oggetti di SnapshotSpan e restituisce un'enumerazione di intervalli di tag.
In c#, implementare questo metodo come iteratore delle prestazioni, che consente la valutazione differita ovvero valutazione impostare solo quando i singoli elementi si accede) dei tag. In Visual Basic, aggiungere i tag a un elenco e restituisce l'elenco.
In questo caso il metodo restituisce un oggetto di TagSpan che ha TextMarkerTag“blue„, che fornisce uno sfondo blu.
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()); } }
creare un provider di Tagger
Per creare il tagger, è necessario implementare IViewTaggerProvider. Questa classe è un elemento del framework MEF, pertanto è necessario impostare gli attributi corretti in modo da riconoscere questa estensione.
Nota
Per ulteriori informazioni su MEF, vedere Managed Extensibility Framework (MEF).
Per creare un provider di tagger
Creare HighlightWordTaggerProvider classe denominata che implementa IViewTaggerProvidere esportarlo con ContentTypeAttribute di “testo„ e TagTypeAttribute di 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
È necessario includere due servizi dell'editor, ITextSearchService e ITextStructureNavigatorSelectorService, per creare un'istanza del tagger.
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; }
Implementare il metodo di CreateTagger``1 per ottenere un'istanza di 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>; }
Compilazione e test del codice
Per testare questo codice, compilare la soluzione di HighlightWordTest ed eseguirla nell'istanza sperimentale.
Per compilare e testare la soluzione di HighlightWordTest
Compilare la soluzione.
Quando si esegue il progetto nel debugger, una seconda istanza di Visual Studio viene creata un'istanza.
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 dovrebbe essere evidenziata in blu.
Vedere anche
Attività
procedura dettagliata: Collegare un tipo di contenuto a un'estensione di file