演练:显示文本

可以将不同的视觉效果编辑器通过创建 managed extensibility framework 组件。 (MEF) 本演练演示了如何显示当前单词的每次出现在文本文件中。 如果运行发生以上的文本文件的一次,,并确定在一个匹配项的插入符号,每个匹配项显示。

系统必备

若要完成本演练,您必须安装 Visual Studio 2010 SDK。

备注

有关 Visual Studio SDK 的更多信息,请参见 扩展 Visual Studio 概述。若要查找有关中所列如何下载 Visual Studio SDK,请 Visual Studio Extensibility Developer Center 参见 MSDN 网站上。

创建 MEF 项目

创建 MEF 项目

  1. 创建一个编辑分类器项目。 将解决方案命名为 HighlightWordTest。

  2. 打开在 VSIX 清单编辑器中的 source.extension.vsixmanifest 文件。

  3. 确保 Content 归为包含一个 MEF 组件内容类型,而 Path 设置为 HighlightWordTest.dll。

  4. 保存并关闭的 source.extension.vsixmanifest。

  5. 删除现有类文件。

定义 TextMarkerTag

在显示文本的第一步是子类 TextMarkerTag 并定义其外观。

定义 TextMarkerTag 和 MarkerFormatDefinition

  1. 将类文件并将其命名为 HighlightWordTag。

  2. 导入下列命名空间。

    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;
    
  3. 创建从 TextMarkerTag 继承的类并将其命名为 HighlightWordTag。

    Friend Class HighlightWordTag
        Inherits TextMarkerTag
    
    internal class HighlightWordTag : TextMarkerTag
    
  4. 创建从 MarkerFormatDefinition继承的第二个类,并将其命名为 HighlightWordFormatDefinition。 为了为标记使用此格式定义,必须导出它具有以下属性:

    <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
    
  5. 在 HighlightWordFormatDefinition 的构造函数中,定义其显示名称和外观。 ,而前景属性定义边框颜色,背景属性定义填充颜色。

    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;
    }
    
  6. 在 HighlightWordTag 的构造函数,请在您创建的格式定义的名称。

    Public Sub New()
        MyBase.New("MarkerFormatDefinition/HighlightWordFormatDefinition")
    End Sub
    
    public HighlightWordTag() : base("MarkerFormatDefinition/HighlightWordFormatDefinition") { }
    

实现 ITagger

下一步是实现 ITagger 接口。 此接口分配,以提供显示文本的给定文本缓冲区、标记和其他可视化效果。

实现标记

  1. 创建实现类型 HighlightWordTagITagger 的类,并将其命名为 HighlightWordTagger。

    Friend Class HighlightWordTagger
        Implements ITagger(Of HighlightWordTag)
    
    internal class HighlightWordTagger : ITagger<HighlightWordTag>
    
  2. 添加以下私有字段和属性添加到类:

    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();
    
  3. 添加初始化前面列出的属性并添加 LayoutChangedPositionChanged 事件处理程序的构造函数。

    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;
    }
    
  4. 事件处理程序的两个调用 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);
    }
    
  5. 您还必须将将通过更新方法调用的 TagsChanged 事件。

    Public Event TagsChanged(ByVal sender As Object, ByVal e As SnapshotSpanEventArgs) _
        Implements ITagger(Of HighlightWordTag).TagsChanged
    
    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
  6. 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));
    }
    
  7. SynchronousUpdate 执行在 WordSpansCurrentWord 属性的一次同步更新,并引发 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)));
        }
    }
    
  8. 必须执行 GetTags 方法。 此方法将 SnapshotSpan 对象的集合并返回标记范围的枚举。

    在 c# 中,将执行此方法作为生成迭代器,启用延迟计算 (即计算,仅当各个项已捕获) 时标记。 在 Visual Basic 中,添加标记到列表中并返回列表。

    此处方法返回具有 “blue” TextMarkerTag,提供蓝色背景的 TagSpan 对象。

    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());
        }
    }
    

创建标记提供程序

若要创建该标记,则必须实现 IViewTaggerProvider。 此类是 MEF 组件部件,因此,必须设置正确的属性,以便此扩展由识别。

备注

有关 MEF 的更多信息,请参见 Managed Extensibility Framework (MEF)

创建标记提供程序

  1. 创建实现 IViewTaggerProvider名为 HighlightWordTaggerProvider 的类,并将其导出与 “text” ContentTypeAttributeTextMarkerTagTagTypeAttribute

    <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
    
  2. 必须导入两个编辑服务, ITextSearchServiceITextStructureNavigatorSelectorService,将实例化标记。

    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; }
    
  3. 执行 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 解决方案

  1. 生成解决方案。

  2. 当您运行在调试器中查看此项目, Visual Studio 的第二个实例进行实例化。

  3. 创建一个文本文件并键入单词重复,例如, “hello " hello " hello”的某些文本。

  4. 将光标放在一个匹配项中 “hello”。 在蓝色应显示每个匹配项。

请参见

任务

演练:链接到文件扩展名的内容类型