IWpfTextView 中的编辑器窗格不可编辑

Posted

技术标签:

【中文标题】IWpfTextView 中的编辑器窗格不可编辑【英文标题】:Editor Pane in IWpfTextView is not Editable 【发布时间】:2022-01-02 13:39:12 【问题描述】:

我正在尝试创建一个 Visual Studio 扩展,该扩展将打开一个文档窗口,其中包含我的自定义控件,该控件承载一个编辑器窗格。我可以加载文档,使用我的自定义内容扩展(分类器,快速信息)加载正确的内容类型,带有显示编辑器窗格加载的自定义控件的窗口,但无法修改文本。我可以选择和突出显示文本,但是除了文本选择之外没有任何键、没有命令和鼠标输入。

以下是我的 Package 类中的内容:

[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideEditorExtension(typeof(MyCustomEditorFactory), ".cust")]
[Guid(MyCustomExtensionsPackage.PackageGuidString)]
public sealed class MyCustomExtensionsPackage : AsyncPackage

    public const string PackageGuidString = "ec2c4646-d0cc-42c6-b0a6-d0ff3e318cef";

    #region Package Members

    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    
        await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            
        RegisterEditorFactory(new MyCustomEditorFactory(this));
    
    #endregion

我已确认调用了 RegisterEditorFactory 方法,并且当打开扩展名为 .cust 的文件时调用了我的编辑器工厂。

以下是我的编辑器工厂:

using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using System;
using System.ComponentModel.Composition;
using System.Runtime.InteropServices;

[ComVisible(true)]
[Guid(EditorFactoryGuidString)]
public sealed class MyCustomEditorFactory : IVsEditorFactory

    public const string EditorFactoryGuidString = "38AD95BA-8891-46A2-A5EA-25F5F36EEAE0";
    private MyCustomExtensionsPackage _package;
    private Microsoft.VisualStudio.OLE.Interop.IServiceProvider _vsServiceProvider;

    [Import]
    public IContentTypeRegistryService ContentTypeRegistry  get; set; 

    [Import]
    public ITextEditorFactoryService TextEditorFactory  get; set; 

    public MyCustomEditorFactory(MyCustomExtensionsPackage package)
    
        _package = package;
    

    public int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
    
        _vsServiceProvider = psp;
        return (VSConstants.S_OK);
    

    public int Close()
    
        return (VSConstants.S_OK);
    

    public int MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView)
    
        pbstrPhysicalView = null;
        return (VSConstants.LOGVIEWID_Primary == rguidLogicalView ? VSConstants.S_OK : VSConstants.E_NOTIMPL);
    

    public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW)
    
        ThreadHelper.ThrowIfNotOnUIThread();

        ppunkDocView = IntPtr.Zero;
        ppunkDocData = IntPtr.Zero;
        pbstrEditorCaption = string.Empty;
        pguidCmdUI = VSConstants.GUID_TextEditorFactory;
        pgrfCDW = 0;
        int retVal = VSConstants.E_FAIL;

        if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) != 0)
        
            IVsTextLines textBuffer = null;

            if (punkDocDataExisting == IntPtr.Zero)
            
                IComponentModel mef = _package.GetService<SComponentModel, IComponentModel>();
                mef.DefaultCompositionService.SatisfyImportsOnce(this);
                IVsEditorAdaptersFactoryService eafs = mef.GetService<IVsEditorAdaptersFactoryService>();

                textBuffer = eafs.CreateVsTextBufferAdapter(_vsServiceProvider, ContentTypeRegistry.GetContentType("CUST")) as IVsTextLines;
                string fileText = System.IO.File.ReadAllText(pszMkDocument);
                textBuffer.InitializeContent(fileText, fileText.Length);

                string[] roles = new string[]
                
                    PredefinedTextViewRoles.Analyzable,
                    PredefinedTextViewRoles.Editable,
                    PredefinedTextViewRoles.Interactive,
                    PredefinedTextViewRoles.Document,
                    PredefinedTextViewRoles.PrimaryDocument
                ;
                IWpfTextView dataView = TextEditorFactory.CreateTextView(eafs.GetDataBuffer(textBuffer), TextEditorFactory.CreateTextViewRoleSet(roles));
                dataView.Options.SetOptionValue(DefaultTextViewHostOptions.LineNumberMarginName, true);
                dataView.Options.SetOptionValue(DefaultTextViewHostOptions.ShowCaretPositionOptionName, true);
                dataView.Options.SetOptionValue(DefaultTextViewHostOptions.ChangeTrackingName, true);
                dataView.Options.SetOptionValue(DefaultTextViewOptions.ViewProhibitUserInputName, false);

                IWpfTextViewHost wpfHost = TextEditorFactory.CreateTextViewHost(dataView, false);
                MyCustomEditor editor = new MyCustomEditor(wpfHost);

                ppunkDocData = Marshal.GetIUnknownForObject(textBuffer);
                ppunkDocView = Marshal.GetIUnknownForObject(editor);

                retVal = VSConstants.S_OK;
            
            else
            
                    
                    //code for document already open
            
            else
            
                retVal = VSConstants.E_INVALIDARG;
            
        
        return (retVal);
    

那里有很多东西要解压,但最终我只是

    从IVsEditorAdaptersFactoryService 创建一个IVsTextBuffer 加载文本缓冲区的内容 使用来自ITextEditorFactoryService 的 IVsTextBuffer 创建一个IWpfTextView 使用来自同一 ITextEditorFactoryService 的 IWpfTextView 创建一个 IWpfTextViewHost 创建我的自定义window pane 并传入IWpfTextViewHost 返回 IVsTextBuffer 作为文档数据和我的自定义窗口窗格作为文档视图

我的自定义窗口窗格代码很简单:

using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Editor;
using System.Runtime.InteropServices;

[ComVisible(true)]
public sealed class MyCustomEditor : WindowPane

    private IWpfTextViewHost _wpfHost;

    public MyCustomEditor(IWpfTextViewHost wpfHost)
    
        _wpfHost = wpfHost;
        Content = new MyCustomEditorControl(wpfHost.HostControl);
    

为了简洁起见,我省略了 MyCustomEditorControl 的代码,但假设它是一个带有 ContentPresenter 的简单 WPF UserControl。构造函数只是使用传入的 wpfHost.HostControl 设置 ContentPresenter 的 Content 属性。

所有这些都可以显示文件的内容,但我无法编辑任何内容。就像输入绑定没有连接或启用一样,但我找不到要在哪些对象上设置或启用哪些属性,并且该主题的文档相当差。我已经浏览了 MSDN 上的 Visual Studio 扩展演练,但它似乎是 WPF 之前和 WPF 之后的 API 的混搭,对于权威方法是什么没有明确的指导。

诚然,我可能天真地假设 IWpfTextView 会自动处理用户输入,但在我的辩护中,它是从 ITextEditorFactoryService 创建的,并且名称中包含“TextEditor”具有某些含义。

我将详细列出我已经尝试和检查过的事情,希望能帮助缩小可能性或我的错误所在:

打开文档窗口后,我检查了正在运行的文档表,在 CreateEditorInstance 退出后自动创建了一个条目。它链接到返回的数据缓冲区、正确的文件路径,并且有一个编辑锁但没有读锁。当 C# 文件在普通编辑器窗口中打开并且具有读写锁时,我检查了 RDT。我也尝试在 RDT 上手动设置读锁,计数器增加,但这似乎没有什么区别。 我检查了数据缓冲区上 ITextBuffer.CheckEditAccess 的返回结果,它返回 true,因此文本缓冲区报告它是可编辑的。 textview 角色对最终呈现的 IWpfTextViewHost 有影响。如果我添加或省略角色,外观和功能会发生变化。例如,添加或删除“ZOOMABLE”会添加或删除带有控件缩放级别的下拉框。但是,“EDITABLE”对可编辑性没有影响。 编辑器选项确实有效。例如,添加或删除行号边距选项会显示/隐藏行号。但是,ViewProhibitUserInputName 对可编辑性没有影响。 我没有使用CreateVsCodeWindowAdapter 创建代码窗口,因为可以从GetWpfTextViewHost 检索的结果IWpfTextViewHost 已经是另一个控件的父级。如果我用 (IWpfTextViewHost.HostControl.Parent as Border).Child = null 断开它,​​那么 IWpfTextViewHost.HostControl 的内容会由于某种原因被处理掉。 IWpfTextViewHost.HostControl 加载并显示一个带有边距和滚动条的窗口,但内容是空白的,如果我单击内容窗格的左边距,则 Visual Studio 会引发 ObjectDisposedException。请注意,我并没有取消主机控件本身或其内容,我只是告诉它现有的父控件它不再有子控件。 我检查了打开我的文档的 IVsWindowFrame 的编辑器 GUID 属性,它设置为 pguidCmdUI 参数中返回的值,我认为这是正确的。 我不确定在编辑器工厂CreateEditorInstance 中为 pguidCmdUI 参数设置的值是否正确。我知道它用于命令路由,这可以解释为什么我不能键入或使用鼠标,但我不确定正确的值应该是什么。我在某处读到它应该是创建编辑器的工厂的 GUID,所以我将它设置为文本编辑器工厂 GUID,因为这就是创建 IWpfTextView 的原因。我尝试将其设置为文件顶部的编辑器工厂 GUID,以及 typeof(IWpfTextView).GUID、Guid.Empty 和其他各种 GUID,但没有任何变化。 如果我将 IWpfTextView 传递给 IVsEditorAdapterFactoryService.GetViewAdapter,我会返回 null。 如果我从IVsEditorAdapterFactoryService.CreateVsTextViewAdapter 创建一个IVsTextView,我可以同时拥有一个IVsTextView 和一个IWpfTextView,但两者互不了解,我也不知道有什么方法可以映射它们。我什至不知道它们是否需要被映射,或者 IVsTextView 是否需要存在或者只是遗留的文本视图界面。 当我检查创建的 IVsTextView 时,它的基本类型是 SimpleTextViewWindow,它是 Microsoft 的 Visual Studio 实现内部的未记录类,它具有 WpfTextView 和 WpfTextViewHost 属性,但它们都是空的(或抛出异常,它们可以'不被阅读)并且是不可设置的。微软显然有一些内部巫术魔法可以在他们自己的代码中将 IVsTextViews 映射到 WpfTextViews,但我不知道它是什么。 我知道创建 IVsInvisibleEditor 并从中获取数据缓冲区的其他代码示例,但这似乎有点 hacky。我的意思是,它在技术上是有效的,似乎没有人知道任何替代方案,因为这方面的文档太差了,所以我不是在评判或批评,只是似乎没有必要这样做。这是我(可能不正确)的理解,隐形编辑器是为在内存中打开但未托管在窗口中的文档创建的虚拟编辑器,但我将我的托管在窗口中。此外,我能找到的所有工作代码示例都是从同一个 Microsoft VSIX 代码示例中复制/粘贴的。在该示例中,虚拟编辑器是在假设文档已经在现有编辑器窗口中打开的情况下创建的工具窗口中创建的。因此,该工具窗口正在为工具窗口创建一个与物理编辑器并排的虚拟编辑器。如果我遵循相同的隐形编辑器方法,那么我实际上是在内存中创建了一个虚拟编辑器,然后是第二个物理文档编辑器窗口。所以基本上,我正在为一个文档创建两个编辑器。这似乎不对。同样,它在技术上是有效的,但似乎不可行。 如果期望我们必须在编辑器窗口上实现 IOleCommandTarget 并手动处理每个按键、快捷键和鼠标按钮并直接操作底层文本缓冲区,那么这将是有史以来最令人沮丧的事情。这意味着他们吹捧能够使用 WPF,但随后为我们提供了一个没有 WPF 控件的文档视图,即使它们存在于框架中,也可以原生处理文本编辑。

很抱歉这篇文章太长了,但我想尽可能多地提供关于我在哪里以及我尝试过的信息。

【问题讨论】:

【参考方案1】:

关于CreateVsCodeWindowAdapter,如何使其工作请看“官方”解释:https://developercommunity.visualstudio.com/t/projectionbuffertutorial-gives-error-in-dev16/498617

【讨论】:

以上是关于IWpfTextView 中的编辑器窗格不可编辑的主要内容,如果未能解决你的问题,请参考以下文章

如何在 AppleScript 脚本编辑器的结果窗格中记录/回显字符串?

如何使pb数据窗口不可编辑变为可编辑

无论 ag-Grid 中的 colDef 可编辑值如何,如何禁用整个网格?

如何在 Xcode 11 中拖动编辑器窗格?

在输入框中的光标后添加不可编辑的文本

js在opera下怎样获取可编辑div中的鼠标光标和选中文本