240
一线老司机

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

posted @ 2010-07-19 08:14  明年我18  阅读(3974)  评论(28编辑  收藏  举报