演练:将快捷键与编辑器扩展配合使用

可以在编辑器扩展中响应快捷键。 以下演练演示如何使用快捷键向文本视图添加视图装饰。 本演练基于视区装饰编辑器模板,它允许你使用 + 字符添加装饰。

创建托管扩展性框架 (MEF) 项目

  1. 创建 C# VSIX 项目。 (在 “新建项目 ”对话框,选择 Visual C# /扩展性,然后选择 VSIX Project。)将解决方案 KeyBindingTest命名为 。

  2. 向项目添加编辑器文本装饰项模板并将其命名 KeyBindingTest。 有关详细信息,请参阅 使用编辑器项模板创建扩展。

  3. 添加以下引用并将 CopyLocal 设置为false

    Microsoft.VisualStudio.Editor

    Microsoft.VisualStudio.OLE.Interop

    Microsoft.VisualStudio.Shell.14.0

    Microsoft.VisualStudio.TextManager.Interop

    在 KeyBindingTest 类文件中,将类名更改为 PurpleCornerBox。 使用左侧边距中显示的灯泡进行其他适当的更改。 在构造函数中,将装饰层的名称从 KeyBindingTest 更改为 PurpleCornerBox

this.layer = view.GetAdornmentLayer("PurpleCornerBox");

在 KeyBindingTestTextViewCreationListener.cs 类文件中,将 AdornmentLayer 的名称从 KeyBindingTest 更改为 PurpleCornerBox

[Export(typeof(AdornmentLayerDefinition))]
[Name("PurpleCornerBox")]
[Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
public AdornmentLayerDefinition editorAdornmentLayer;

处理 TYPECHAR 命令

在 Visual Studio 2017 版本 15.6 之前,在编辑器扩展中处理命令的唯一 IOleCommandTarget 方法是实现基于命令筛选器。 Visual Studio 2017 版本 15.6 引入了基于编辑器命令处理程序的新式简化方法。 接下来的两个部分演示了如何使用旧方法和新式方法处理命令。

定义命令筛选器(Visual Studio 2017 版本 15.6 之前)

命令筛选器是一种实现 IOleCommandTarget,它通过实例化装饰来处理命令。

  1. 添加一个类文件并将其命名为 KeyBindingCommandFilter

  2. 添加以下 using 指令。

    using System;
    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio.OLE.Interop;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Text.Editor;
    
    
  3. 名为 KeyBindingCommandFilter 的类应继承自 IOleCommandTarget.

    internal class KeyBindingCommandFilter : IOleCommandTarget
    
  4. 为文本视图添加专用字段、命令链中的下一个命令,以及用于表示是否已添加命令筛选器的标志。

    private IWpfTextView m_textView;
    internal IOleCommandTarget m_nextTarget;
    internal bool m_added;
    internal bool m_adorned;
    
  5. 添加设置文本视图的构造函数。

    public KeyBindingCommandFilter(IWpfTextView textView)
    {
        m_textView = textView;
        m_adorned = false;
    }
    
  6. QueryStatus()按如下所示实现该方法。

    int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  7. 实现该方法 Exec() ,以便在键入加号 (+) 字符时向视图添加紫色框。

    int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
    {
        if (m_adorned == false)
        {
            char typedChar = char.MinValue;
    
            if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
            {
                typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
                if (typedChar.Equals('+'))
                {
                    new PurpleCornerBox(m_textView);
                    m_adorned = true;
                }
            }
        }
        return m_nextTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
    }
    
    

添加命令筛选器(Visual Studio 2017 版本 15.6 之前)

装饰提供程序必须将命令筛选器添加到文本视图。 在此示例中,提供程序实现 IVsTextViewCreationListener 侦听文本视图创建事件。 此装饰提供程序还导出装饰层,该层定义装饰的 Z 顺序。

  1. 在 KeyBindingTestTextViewCreationListener 文件中,添加以下 using 指令:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.OLE.Interop;
    using Microsoft.VisualStudio.Utilities;
    using Microsoft.VisualStudio.Editor;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.TextManager.Interop;
    
    
  2. 若要获取文本视图适配器,必须导入 。IVsEditorAdaptersFactoryService

    [Import(typeof(IVsEditorAdaptersFactoryService))]
    internal IVsEditorAdaptersFactoryService editorFactory = null;
    
    
  3. TextViewCreated更改方法,使其添加 KeyBindingCommandFilter.

    public void TextViewCreated(IWpfTextView textView)
    {
        AddCommandFilter(textView, new KeyBindingCommandFilter(textView));
    }
    
  4. 处理程序 AddCommandFilter 获取文本视图适配器并添加命令筛选器。

    void AddCommandFilter(IWpfTextView textView, KeyBindingCommandFilter commandFilter)
    {
        if (commandFilter.m_added == false)
        {
            //get the view adapter from the editor factory
            IOleCommandTarget next;
            IVsTextView view = editorFactory.GetViewAdapter(textView);
    
            int hr = view.AddCommandFilter(commandFilter, out next);
    
            if (hr == VSConstants.S_OK)
            {
                commandFilter.m_added = true;
                 //you'll need the next target for Exec and QueryStatus
                if (next != null)
                commandFilter.m_nextTarget = next;
            }
        }
    }
    

实现命令处理程序(从 Visual Studio 2017 版本 15.6 开始)

首先,更新项目的 Nuget 引用以引用最新的编辑器 API:

  1. 右键单击项目,然后选择“ 管理 Nuget 包”。

  2. Nuget 程序包管理器中,选择“汇报”选项卡,选择“选择所有包”检查框,然后选择“更新”。

命令处理程序是一种实现 ICommandHandler<T>,它通过实例化装饰来处理命令。

  1. 添加一个类文件并将其命名为 KeyBindingCommandHandler

  2. 添加以下 using 指令。

    using Microsoft.VisualStudio.Commanding;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
    using Microsoft.VisualStudio.Utilities;
    using System.ComponentModel.Composition;
    
  3. 名为 KeyBindingCommandHandler 的类应继承, ICommandHandler<TypeCharCommandArgs>并将其导出为 ICommandHandler

    [Export(typeof(ICommandHandler))]
    [ContentType("text")]
    [Name("KeyBindingTest")]
    internal class KeyBindingCommandHandler : ICommandHandler<TypeCharCommandArgs>
    
  4. 添加命令处理程序的显示名称:

    public string DisplayName => "KeyBindingTest";
    
  5. GetCommandState()按如下所示实现该方法。 由于此命令处理程序处理核心编辑器 TYPECHAR 命令,因此它可以将启用命令委托给核心编辑器。

    public CommandState GetCommandState(TypeCharCommandArgs args)
    {
        return CommandState.Unspecified;
    }
    
  6. 实现该方法 ExecuteCommand() ,以便在键入加号 (+) 字符时向视图添加紫色框。

    public bool ExecuteCommand(TypeCharCommandArgs args, CommandExecutionContext executionContext)
    {
        if (args.TypedChar == '+')
        {
            bool alreadyAdorned = args.TextView.Properties.TryGetProperty(
                "KeyBindingTextAdorned", out bool adorned) && adorned;
            if (!alreadyAdorned)
            {
                new PurpleCornerBox((IWpfTextView)args.TextView);
                args.TextView.Properties.AddProperty("KeyBindingTextAdorned", true);
            }
        }
    
        return false;
    }
    
    1. KeyBindingTestTextViewCreationListener.cs 文件中的装饰层定义复制到 KeyBindingCommandHandler.cs ,然后删除 KeyBindingTestTextViewCreationListener.cs 文件:
    /// <summary>
    /// Defines the adornment layer for the adornment. This layer is ordered
    /// after the selection layer in the Z-order.
    /// </summary>
    [Export(typeof(AdornmentLayerDefinition))]
    [Name("PurpleCornerBox")]
    [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)]
    private AdornmentLayerDefinition editorAdornmentLayer;
    

使装饰出现在每一行上

原始装饰出现在文本文件中的每个字符“a”上。 现在,我们已更改代码以添加装饰以响应 + 字符,因此它仅在键入字符的 + 行中添加装饰。 我们可以更改装饰代码,使装饰再次出现在每个“a”上。

KeyBindingTest.cs 文件中,更改 CreateVisuals() 方法以循环访问视图中的所有行以修饰“a”字符。

private void CreateVisuals(ITextViewLine line)
{
    IWpfTextViewLineCollection textViewLines = this.view.TextViewLines;

    foreach (ITextViewLine textViewLine in textViewLines)
    {
        if (textViewLine.ToString().Contains("a"))
        {
            // Loop through each character, and place a box around any 'a'
            for (int charIndex = textViewLine.Start; charIndex < textViewLine.End; charIndex++)
            {
                if (this.view.TextSnapshot[charIndex] == 'a')
                {
                    SnapshotSpan span = new SnapshotSpan(this.view.TextSnapshot, Span.FromBounds(charIndex, charIndex + 1));
                    Geometry geometry = textViewLines.GetMarkerGeometry(span);
                    if (geometry != null)
                    {
                        var drawing = new GeometryDrawing(this.brush, this.pen, geometry);
                        drawing.Freeze();

                        var drawingImage = new DrawingImage(drawing);
                        drawingImage.Freeze();

                        var image = new Image
                        {
                            Source = drawingImage,
                        };

                        // Align the image with the top of the bounds of the text geometry
                        Canvas.SetLeft(image, geometry.Bounds.Left);
                        Canvas.SetTop(image, geometry.Bounds.Top);

                        this.layer.AddAdornment(AdornmentPositioningBehavior.TextRelative, span, null, image, null);
                    }
                }
            }
        }
    }
}

生成并测试代码

  1. 生成 KeyBindingTest 解决方案,并在实验实例中运行它。

  2. 创建或打开文本文件。 键入一些包含字符“a”的单词,然后在文本视图中的任意位置键入 +

    文件中的每个“a”字符上应显示一个紫色方块。