• VsxHowTo把Windows Forms Designer作为自己的编辑器(1)


         有时候我们想实现一个表单设计器,在这个设计器实现拖动控件、设置属性等功能。VS内置的WinForm Designer无疑是最好的选择,那么,我们怎样才能把它作为自己的编辑器呢?

         首先,我们来看一下VS编辑器的结构,下图摘自LearnVSXNow

    image

         从上图可以看出,要实现一个编辑器,实现需要Editor Factory、Document Data和Document View。其中,我们不需要再实现Document View了,因为我们要重用VS的Winform Designer,它就是Document View,我们的目的就是把它调用出来。

         另外,我们只实现单视图的编辑器。

         首先,我们先来创建一个VSPackage项目,项目名称为“WinFormDesigner”,不用添加ToolWindow和Menu。接下来就要实现Document Data和Editor Factory了。

    实现Document Data

         添加一个DocumentData的类,这一次我们只让它实现IVsPersistDocData接口,其他两个接口IPersistFileFormat和IOleCommandTarget我们以后再实现:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio;
     
    namespace Company.WinFormDesigner
    {
        class DocumentData : IVsPersistDocData
        {
            private Guid _factoryGuid = typeof(DocumentEditorFactory).GUID;
     
            #region IVsPersistDocData 成员
     
            int IVsPersistDocData.Close()
            {
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.GetGuidEditorType(out Guid pClassID)
            {
                pClassID = _factoryGuid;
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.IsDocDataDirty(out int pfDirty)
            {
                pfDirty = 0;
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.IsDocDataReloadable(out int pfReloadable)
            {
                pfReloadable = 1;
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.LoadDocData(string pszMkDocument)
            {
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.OnRegisterDocData(uint docCookie, IVsHierarchy pHierNew, uint itemidNew)
            {
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.ReloadDocData(uint grfFlags)
            {
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.RenameDocData(uint grfAttribs, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew)
            {
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.SaveDocData(VSSAVEFLAGS dwSave, out string pbstrMkDocumentNew, out int pfSaveCanceled)
            {
                pbstrMkDocumentNew = null;
                pfSaveCanceled = 0;           
                return VSConstants.S_OK;
            }
     
            int IVsPersistDocData.SetUntitledDocPath(string pszDocDataPath)
            {
                return VSConstants.S_OK;
            }
     
            #endregion
        }
    }

         从代码里可以看到,DocumentData这个类只是简单的实现了IVsPersistDocData接口,所有的方法只是简单的返回VSConstants.S_OK,并没有真正实现诸如LoadDocData和SaveDocData这样的方法。这是因为这篇文章的目的是如何重用WinForm Designer,而暂不涉及文件的读取和存储,我会在后续的文章里逐步完善DocumentData。

    实现Editor Factory

         添加类DocumentEditorFactory,并实现IVsEditorFactory接口。我们的目的,是要在IVsEditorFactory.CreateEditorInstance方法里,调出VS的form designer,并赋值给out参数ppunkDocView。

         在这里我们需要利用Microsoft.VisualStudio.Designer.Interfaces.IVSMDDesignerService接口(要使用该接口,要添加对Microsoft.VisualStudio.Designer.Interfaces程序集的引用)的CreateDesigner方法,该方法接受两个参数,第一个参数是Microsoft.VisualStudio.OLE.Interop.IServiceProvider,第二个参数是DesignerLoader,所以,我们先要添加一个DesignerLoader的类,如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.Design.Serialization;
    using System.Windows.Forms;
     
    namespace Company.WinFormDesigner
    {
        class DesignerLoader : BasicDesignerLoader
        {       
            protected override void PerformFlush(IDesignerSerializationManager serializationManager)
            {            
            }
     
            protected override void PerformLoad(IDesignerSerializationManager serializationManager)
            {
                LoaderHost.Container.Add(new UserControl());
            }
        }
    }

         我们的DesignerLoader类也只是“稍微实现”了一下,只是在PerformLoad的时候往LoaderHost里加了一个UserControl。这样LoaderHost的RootComponent就是一个UserControl了,在设计器加载的时候就会加载UserControl的RootDesigner。这其实也是我们重用WinForm Designer的最关键的一步,我们其他的代码都是为了这句服务的,因为VS加载什么设计器,是由DesignerHost的RootComponent的RootDesigner决定的,不清楚的同学可以google一下IRootDesigner。

         有了DesignerLoader之后,就可以实现DocumentEditorFactory了,该类的实现如下:

    using System;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Designer.Interfaces;
    using Microsoft.VisualStudio.OLE.Interop;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio.TextManager.Interop;
    using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
    using IServiceProvider = System.IServiceProvider;
    using System.ComponentModel.Design;
     
    namespace Company.WinFormDesigner
    {
        class DocumentEditorFactory : IVsEditorFactory
        {
            private IServiceProvider serviceProvider;
     
            #region IVsEditorFactory 成员
     
            public int Close()
            {
                return VSConstants.S_OK;
            }
     
            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)
            {
                // Initialize out parameters
                ppunkDocView = IntPtr.Zero;
                ppunkDocData = IntPtr.Zero;
                pguidCmdUI = Guid.Empty;
                pgrfCDW = 0;
                pbstrEditorCaption = string.Empty;
     
                // Validate inputs
                if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0)
                    return VSConstants.E_INVALIDARG;
     
     
                try
                {
                    var designerService = serviceProvider.GetService(typeof(IVSMDDesignerService)) as IVSMDDesignerService;
                    var oleServiceProvider = serviceProvider.GetService(typeof(IOleServiceProvider)) as IOleServiceProvider;
                    var designerLoader = new DesignerLoader();
     
                    IVSMDDesigner designer = designerService.CreateDesigner(oleServiceProvider, designerLoader);
     
                    object designerView = designer.View;
                    pguidCmdUI = designer.CommandGuid;
                    ppunkDocView = Marshal.GetIUnknownForObject(designerView);
     
                    var data = new DocumentData();
                    ppunkDocData = Marshal.GetIUnknownForObject(data);
                }            
                finally
                {
                    if (ppunkDocView == IntPtr.Zero)
                    {
                        if (punkDocDataExisting != ppunkDocData && ppunkDocData != IntPtr.Zero)
                        {
                            Marshal.Release(ppunkDocData);
                            ppunkDocData = IntPtr.Zero;
                        }
                    }
                }            
     
                return VSConstants.S_OK;
            }
     
     
            public int MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView)
            {
                pbstrPhysicalView = null; // --- Initialize out parameter
     
                // --- We support only a single physical view
                if (VSConstants.LOGVIEWID_Primary == rguidLogicalView)
                {
                    // --- Primary view uses NULL as physicalView
                    return VSConstants.S_OK;
                }
                else
                {
                    // --- You must return E_NOTIMPL for any unrecognized logicalView values
                    return VSConstants.E_NOTIMPL;
                }
            }
     
            public int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
            {
                serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider(psp);
                return VSConstants.S_OK;
            }
     
            #endregion        
        }
    }

         由于我们只需要做单视图的设计器,所以在MapLogicalView方法里,只在rguidLogicalView参数为VSConstants.LOGVIEWID_Primary的时候返回VSConstants.S_OK。

         在CreateEditorInstance方法里,利用serviceProvider得到IVSMDDesignerService和Microsoft.VisualStudio.OLE.Interop.IServiceProvider的实例,接着创建了一个DesignerLoader的实例,然后就调用IVSMDDesignerService.CreateDesigner方法创建了一个IVSMDDesigner的对象,该对象的View属性,就是我们要的Winform Designer。最后,把View和DocumentData对象的指针赋给相应的out参数。

    注册Editor Factory

         注册DocumentEditorFactory的方法和注册其他Editor Factory的方法一样,即在Package初始化的时候,调用RegisterEditorFactory方法。并为Package类添加一个ProvideEditorExtension的Attribute声明:

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio.Shell;
     
    namespace Company.WinFormDesigner
    {   
        [PackageRegistration(UseManagedResourcesOnly = true)]
        [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]
        [InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]
        [ProvideLoadKey("Standard", "1.0", "WinFormDesigner", "Company", 1)]
        [Guid(GuidList.guidWinFormDesignerPkgString)]
        //将EditorFactory和文件扩展名关联起来
        [ProvideEditorExtension(typeof(DocumentEditorFactory), ".form", 100)]
        public sealed class WinFormDesignerPackage : Package
        {
            public WinFormDesignerPackage()
            {
                Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
            }
     
            #region Package Members
     
            protected override void Initialize()
            {
                Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
                base.Initialize();
     
                //注册Editor Factory
                RegisterEditorFactory(new DocumentEditorFactory());
     
            }
     
            #endregion
        }
    }

         在这里,我们把DocumentEditorFactory好*.form文件关联了起来。

    测试我们的设计器

         新建一个文本文件,并把扩展名改为.form,然后用vs Experimental hive打开,可以看到VS加载了Winform设计器:

    image

         但这个设计器是有问题的,例如拖动控件到设计器后,控件没有自动命名;文档窗口也不会随着修改而自动加入*的标记;不能undo/redo;当然,最大的问题,不能保存数据。

         让我们在后续文章里逐步完善这个设计器。

    代码下载:WinFormDesigner.rar

  • 相关阅读:
    数据库优化
    Oracle语句集锦
    MVC Razor标签
    转载 操作MyBatis基础
    mysql sqlserver Oracle字符串连接
    Word
    部署IIS错误
    => 朗姆达表达式带入符号
    wcf例子01
    idea通过springboot初始化器新建项目
  • 原文地址:https://www.cnblogs.com/default/p/1780438.html
Copyright © 2020-2023  润新知