연습: 텍스트 강조 표시
확장성 프레임 워크 (MEF) 관리 되는 구성 요소를 작성 하 여 편집기에 다양 한 시각적 효과 추가할 수 있습니다. 이 연습에서는 텍스트 파일에서 현재 단어의 모든 항목을 강조 표시 하는 방법을 보여 줍니다. Word는 텍스트 파일에 두 번 이상 발생 하나 발견 된 캐럿을 배치할 경우 모두 강조 표시 됩니다.
사전 요구 사항
이 연습을 완료 하려면 설치 해야 해당 Visual Studio 2010 SDK.
참고
Visual Studio SDK에 대 한 자세한 내용은 참조 하십시오. Visual Studio 개요를 확장합니다..Visual Studio SDK를 다운로드 하는 방법를 참조 하십시오. Visual Studio 확장성 개발자 센터 MSDN 웹 사이트에서.
MEF 프로젝트 만들기
MEF 프로젝트를 만들려면
편집기 분류자 프로젝트를 만듭니다. 솔루션의 이름을 HighlightWordTest.
VSIX 매니페스트 편집기에서 source.extension.vsixmanifest 파일을 엽니다.
Content MEF 구성 요소 콘텐츠 형식 및 해당 제목 포함은 Path Highlightwordtest.dll으로 설정 됩니다.
저장 하 고 source.extension.vsixmanifest를 닫습니다.
기존 클래스 파일을 삭제 합니다.
정의 하는 TextMarkerTag
텍스트를 강조 표시 하려면 먼저 하위 클래스에는 TextMarkerTag 와 그 모양을 정의 합니다.
TextMarkerTag 및 Markerformatdefinition를 정의 하려면
클래스 파일을 추가 하 고 HighlightWordTag 이름을 지정 합니다.
다음 네임 스페이스를 가져옵니다.
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;
상속 하는 클래스를 만드는 TextMarkerTag 하 고 이름을 HighlightWordTag.
Friend Class HighlightWordTag Inherits TextMarkerTag
internal class HighlightWordTag : TextMarkerTag
상속 되는 두 번째 클래스 만들기 MarkerFormatDefinition, 및 HighlightWordFormatDefinition 이름. 이 형식 정의 대 한 태그를 사용 하려면 다음과 같은 특성을 내보내야 합니다.
NameAttribute: 태그 사용이이 형식을 참조 하려면
UserVisibleAttribute:이 형식을 UI에 표시 됩니다
<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
Highlightwordformatdefinition의 생성자에 해당 표시 이름 및 모양을 정의 합니다. Foreground 속성의 테두리 색을 정의 하는 동안 배경 속성 채우기 색을 정의 합니다.
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; }
Highlightwordtag에 대 한 생성자에서 방금 만든 형식 정의의 이름을 전달 합니다.
Public Sub New() MyBase.New("MarkerFormatDefinition/HighlightWordFormatDefinition") End Sub
public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
Itagger를 구현합니다.
다음 단계를 구현 하는 것은 ITagger 인터페이스입니다. 이 인터페이스에서 주어진된 텍스트 버퍼, 텍스트를 강조 표시를 제공 하는 태그 및 기타 시각적 효과를 할당 합니다.
Tagger 구현 하기
구현 하는 클래스를 만들 ITagger 형식 HighlightWordTag를 하 고 이름을 HighlightWordTagger.
Friend Class HighlightWordTagger Implements ITagger(Of HighlightWordTag)
internal class HighlightWordTagger : ITagger<HighlightWordTag>
다음과 같은 private 필드 및 속성을 클래스에 추가 합니다.
ITextView를 현재 보기에 해당 합니다.
ITextBuffer, 텍스트 뷰의 기반이 되는 텍스트 버퍼에 해당 합니다.
ITextSearchService, 텍스트를 찾기 위해 사용 됩니다.
ITextStructureNavigator, 텍스트 범위 내에서 탐색 하는 방법이.
A NormalizedSnapshotSpanCollection을 강조 하는 단어의 집합을 포함 합니다.
A SnapshotSpan, 현재 단어에 해당 합니다.
A SnapshotPoint, 캐럿의 현재 위치에 해당 합니다.
잠금 개체입니다.
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();
앞에 나열 된 속성을 초기화 하 고 추가 생성자를 추가 LayoutChanged 및 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; }
모두 호출할 이벤트 처리기는 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); }
또한 추가 해야는 TagsChanged update 메서드로 호출 되는 이벤트입니다.
Public Event TagsChanged(ByVal sender As Object, ByVal e As SnapshotSpanEventArgs) _ Implements ITagger(Of HighlightWordTag).TagsChanged
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
UpdateAtCaretPosition() 메서드는 커서를 배치 되 고 목록이 생성 단어를 동일한 텍스트 버퍼에서 모든 단어를 찾습니다 SnapshotSpan 는 단어의 항목에 해당 하는 개체입니다. 그런 다음 호출 SynchronousUpdate, 어떤 발생을 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 의 동기 업데이트를 수행의 WordSpans 및 CurrentWord 속성 및 발생의 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))); } }
구현 해야는 GetTags 메서드가 있습니다. 이 메서드는 컬렉션을 사용 SnapshotSpan 개체 및 span 태그의 열거형을 반환 합니다.
C#이 메서드는 태그의 지연 계산 (집합은 개별 항목에 액세스할 때의 평가)가 yield 반복기로 구현 하는 데 사용 합니다. Visual Basic 태그 목록에 추가 하 고 목록 값을 반환 합니다.
여기 메서드를 반환는 TagSpan 개체에 "파란색" TextMarkerTag, 파란색 배경을 제공 합니다.
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()); } }
Tagger 공급자 만들기
Tagger를 만들기 위해 구현 해야는 IViewTaggerProvider. 이 클래스는 MEF 구성 요소 부품을 이므로이 확장명을 인식할 수 있는 올바른 특성을 설정 합니다.
참고
MEF에 대한 자세한 내용은 MEF(Managed Extensibility Framework)를 참조하십시오.
Tagger 공급자를 만들려면
라는 클래스를 만듭니다 HighlightWordTaggerProvider 는 구현 IViewTaggerProvider, 함께 내보낼는 ContentTypeAttribute "텍스트"와 a TagTypeAttribute 의 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
두 편집기 서비스에서 가져와야 합니다는 ITextSearchService 및 ITextStructureNavigatorSelectorService, 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; }
구현에서 CreateTagger``1 의 인스턴스를 반환 하는 방법을 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>; }
빌드 및 코드 테스트
이 코드를 테스트 하려면 HighlightWordTest 솔루션을 빌드하고 실험 인스턴스를 실행 합니다.
빌드 및 HighlightWordTest 솔루션을 테스트 하려면
솔루션을 빌드합니다.
이 프로젝트를 디버거에서 실행할 때 Visual Studio 두 번째 인스턴스를 인스턴스화합니다.
예를 들어, "hello hello hello" 라는 단어가 반복 되는 텍스트를 입력 하 고 텍스트 파일을 만듭니다.
"Hello"의 항목 중 하나에 커서를 놓습니다. 모두 파란색으로 강조 표시 됩니다.