VSPackge插件系列:简单文本编辑器的实现

   相比其它开发环境,VS的好用就不用多说了,尽管VS很人性化,但是针对具体的我们想实现的功能时,会力不从心,也许会有很多现成的插件,但是作为一名程序员,我还是喜欢自己去写一些东西,因为这样能随心所欲的想做什么就做什么。

  开始做事之前,我们不得不做很多的准备工作,比如说VS sp1我们得安装,理解vs插件有哪些,我们也都查不少文章,看很多哪些令人讨厌的msdn, 这些都不重要,重要的是,结果我们总会做出一个vs插件,哪些只是在磨刀而已。为什么我们会选VSPackge插件,而不是宏命令,或者addin。我们如何快速建立一个工程,我们的会花费不少时间去解决这些疑问。

  下面是我参考的文章,希望对大家有所帮助。这里就不具体的一步步写那些疑问步骤了。

http://www.cnblogs.com/default/archive/2010/07/17/1779563.html

  好吧,今天的主题就是开发一个简单的文本编辑器插件。没有视觉的文章看起来总是让人头疼,尤其是看了几十页的文字最终不知道到底说什么。来看下做编辑器最少需要做什么。

  做VS的编辑器,我们得建立一个Package,这个向导会帮我们生成的,另外,我们要做的就是建立一个EditFactory,再建立一个Editor即可。当然从图上可以看到,我们可以在一个Package里面建立多个Editor。为了将共性的东西放在一起,我对Factory与Editor都建立了基类,这样Factory与Editor里的东西就会少不少,于是就有了下图的结构。

大体上结构有了,我们的目标明确了,下一步,为了和VS交互我们不得不继承一些接口。

 

这些接口都是干什么的,我们得简单的介绍下,更详细的我已经把msdn的地址粘贴的下面了,方便大家访问

IVsEditorFactory 接口,编辑器工厂类的接口,用于创建编辑器实例。


 名称说明
公共方法 Close 释放所有缓存的接口指针,所有事件接收的注销。
公共方法 CreateEditorInstance 用于使编辑工厂体系结构创建支持数据/视图分开的编辑器。
公共方法 MapLogicalView 映射逻辑视图到一个物理视图。
公共方法 SetSite 在环境中初始化的编辑器。
msdn:地址

 

IVsPersistDocData 接口 文档处理接口


 名称说明
公共方法 Close 关闭 IVsPersistDocData 对象。
公共方法 GetGuidEditorType 返回创建 IVsPersistDocData 编辑对象工厂的唯一标识符。
公共方法 IsDocDataDirty 确定文档是否已更改,因为次保存。
公共方法 IsDocDataReloadable 确定文档是否可重新加载。
公共方法 LoadDocData 从给定 MkDocument 将文档加载数据。
公共方法 OnRegisterDocData 调用通过运行文档表 (RDT),则注册在 RDT 的文档数据。
公共方法 ReloadDocData 重新加载文档数据,并在此过程中确定是否忽略一个后续文件更改。
公共方法 RenameDocData 将文档数据重命名。
公共方法 SaveDocData 将文档保存数据。
公共方法 SetUntitledDocPath 设置初始名称 (或路径) 未保存的,新创建文档数据。
msdn:地址
 
 

IOleCommandTarget 接口 命令处理接口


 名称说明
公共方法 Exec 执行指定的命令。
公共方法 QueryStatus 查询该对象以获得由用户界面事件生成的一个或多个命令的状态。
msdn:地址
 

IPersistFileFormat 接口 文件处理接口


 名称说明
公共方法 GetClassID(Guid) (继承自 IPersist。)
公共方法 GetClassID(Guid)  
公共方法 GetCurFile 返回路径到对象的当前工作文件,或者,如果没有一种当前工作文件,对象的默认文件名提示。
公共方法 GetFormatList 提供该调用方提供必要的信息委托对象打开标准常见 保存 对话框 (使用 GetSaveFileNameViaDlg 函数)。
公共方法 InitNew 在没有权限的状态指示对象初始化自身。
公共方法 IsDirty 确定对象是否以保存更改为其当前文件。
公共方法 Load 打开已指定的文件并初始化从文件内容的对象。
公共方法 Save 将该对象的副本保存到指定文件。
公共方法 SaveCompleted 通知对象可以推断保存事务,并且对象可以写入它的文件。
msdn:地址
 
 
 在正式开始之前,我们还得有些东西需要了解下,那就是我们使用什么窗体。
 
  有了这些,还不足以令我们对做一个插件编辑器有很好的理解,不管做什么事情总有一些东西需要我们值得注意,比如骑摩托上坡时,到顶的时候要减油门。vs编辑器也是,我们还需要注意的是,在package上面要声明一些特性,还需要在初始化的时候注册我们的工厂类。
 
   [PackageRegistration(UseManagedResourcesOnly = true)]
    [ProvideEditorFactory(typeof(ContextEditorFactory), 200, TrustLevel = __VSEDITORTRUSTLEVEL.ETL_AlwaysTrusted)]
    [ProvideEditorExtension(typeof(ContextEditorFactory), ".cs", 50, DefaultName = "file")]
    [ProvideEditorLogicalView(typeof(ContextEditorFactory), GuidList.GuidGL_IDE_VSPackageEditorFactoryString)]
    [Guid(GuidList.guidGL_IDE_VSPackagePkgString)]
    public sealed class GL_IDE_VSPackagePackage : Package
    {
        public GL_IDE_VSPackagePackage()
        {

        }

        #region Package Members

        protected override void Initialize()
        {
            base.Initialize();
            RegisterEditorFactory(new ContextEditorFactory(this));
        }
        #endregion
    }


  这里又引出一些新的东西,对于新鲜的事物,小的时候我们总是充满了好奇,现在的我们却对新的东西失去了兴趣,甚至有时拒绝去理解。是什么时候我们改变了,已经记不起。

关于这些特性介绍,就是注册编辑器工厂类,编辑器扩展文件名,编辑器视图什么的,这里有篇文章大家可以参考。

http://www.cnblogs.com/default/archive/2011/06/11/2078501.html

更多的特性有兴趣的可以查msdn。

下面看下我们工厂基类的实现:

 public abstract class EditorFactoryBase<TEditorPane> : IVsEditorFactory, IDisposable
        where TEditorPane : WindowPane, IOleCommandTarget, IVsPersistDocData, IPersistFileFormat, new()
    {

        private ServiceProvider _ServiceProvider;

        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)
        {
            ppunkDocView = IntPtr.Zero;
            ppunkDocData = IntPtr.Zero;
            pbstrEditorCaption = null;
            pguidCmdUI = GetType().GUID;
            pgrfCDW = 0;

            // --- Validate inputs  
            if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0)
            {
                return VSConstants.E_INVALIDARG;
            }
            if (punkDocDataExisting != IntPtr.Zero)
            {
                return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
            }
            // --- Create the Document (editor)  
            TEditorPane newEditor = new TEditorPane();
            ppunkDocView = Marshal.GetIUnknownForObject(newEditor);
            ppunkDocData = Marshal.GetIUnknownForObject(newEditor);
            pbstrEditorCaption = "";

            return VSConstants.S_OK;
        }

        public int MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView)
        {
            pbstrPhysicalView = null;
            if (VSConstants.LOGVIEWID_Primary == rguidLogicalView)
            {
                return VSConstants.S_OK;
            }
            else
            {
                return VSConstants.E_NOTIMPL;
            }
        }

        public int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
        {
            _ServiceProvider = new ServiceProvider(psp);
            return VSConstants.S_OK;
        }

        public void Dispose()
        {
            if (_ServiceProvider != null)
            {
                _ServiceProvider.Dispose();
            }
        }


        public object GetService(Type serviceType) 
        { 
            return _ServiceProvider.GetService(serviceType); 
        }
    }

接下来就是编辑器基类的实现:有很多功能还未实现,但这已经足够我们简单编辑器的功能了

 public abstract class EditorPaneBase<TFactory, TUIControl> : WindowPane, IOleCommandTarget, IVsPersistDocData, IPersistFileFormat
        where TFactory : IVsEditorFactory
        where TUIControl : Control, ISimpleEditor, new()
    {
        private Guid _factoryGuid = typeof(TFactory).GUID;

        private IVsUIShell _vsUiShell = null;

        private TUIControl _editor = null;

        private const char endline = '\n';

        private const uint FormatIndex = 0;

        private string FileName { get; set; }

        public EditorPaneBase()
        {
            _vsUiShell = ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
        }

        #region IVsPersistDocData

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

        public int GetGuidEditorType(out Guid pClassID)
        {
            pClassID = _factoryGuid; 
            return VSConstants.S_OK;
        }

        public int IsDocDataDirty(out int pfDirty)
        {
            pfDirty = 0; 
            return VSConstants.S_OK;
        }

        public int IsDocDataReloadable(out int pfReloadable)
        {
            pfReloadable = 1;
            return VSConstants.S_OK;
        }

        public int LoadDocData(string pszMkDocument)
        {
            _editor.LoadDocData(pszMkDocument);
            this.FileName = pszMkDocument;
            return VSConstants.S_OK;
        }

        public int OnRegisterDocData(uint docCookie, IVsHierarchy pHierNew, uint itemidNew)
        {
            _editor = this.Content as TUIControl;
            return VSConstants.S_OK;
        }

        public int ReloadDocData(uint grfFlags)
        {
            return VSConstants.S_OK;
        }

        public int RenameDocData(uint grfAttribs, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew)
        {
            return VSConstants.S_OK;
        }

        public int SaveDocData(VSSAVEFLAGS dwSave, out string pbstrMkDocumentNew, out int pfSaveCanceled)
        {
            pbstrMkDocumentNew = null;
            pfSaveCanceled = 0; 
            return VSConstants.S_OK;
        }

        public int SetUntitledDocPath(string pszDocDataPath)
        {
            return VSConstants.S_OK;
        }

        #endregion IVsPersistDocData

        #region IPersistFileFormat

        public int GetClassID(out Guid pClassID)
        {
            pClassID = typeof(TFactory).GUID;
            return VSConstants.S_OK;
        }

        public int GetCurFile(out string ppszFilename, out uint pnFormatIndex)
        {
            pnFormatIndex = FormatIndex;
            ppszFilename = FileName;
            return VSConstants.S_OK;
        }

        public int GetFormatList(out string ppszFormatList)
        {

            var formatList = string.Format(CultureInfo.InvariantCulture, "My Editor (*{0}){1}*{0}{1}{1}", ".cs", endline);
            ppszFormatList = formatList;

            return VSConstants.S_OK;
        }

        public int InitNew(uint nFormatIndex)
        {
            if (nFormatIndex != FormatIndex)
            {
                return VSConstants.E_INVALIDARG;
            }
            _editor.IsDirty = false;
            return VSConstants.S_OK;
        }

        public int IsDirty(out int pfIsDirty)
        {
            pfIsDirty = _editor.IsDirty ? 1 : 0;
            return VSConstants.S_OK;
        }

        public int Load(string pszFilename, uint grfMode, int fReadOnly)
        {
            if (pszFilename == null)
            {
                return VSConstants.E_INVALIDARG;
            }
            _vsUiShell.SetWaitCursor();

            throw new NotImplementedException();
        }

        public int Save(string pszFilename, int fRemember, uint nFormatIndex)
        {
            _editor.Save();
            return VSConstants.S_OK;
        }

        public int SaveCompleted(string pszFilename)
        {
            //TODO:Editor SaveCompleted
            return VSConstants.S_OK;
        }

        #endregion IPersistFileFormat

        #region IEditorCommonCommand

        public void DoSelectAll(object sender, EventArgs e)
        {
            _editor.DoSelectAll();
        }

        public void DoCopy(object sender, EventArgs e)
        {
            _editor.DoCopy();
        }

        public void DoCut(object sender, EventArgs e)
        {
            _editor.DoCut();
        }

        public void DoPaste(object sender, EventArgs e)
        {
            _editor.DoPaste();
        }

        public void DoRedo(object sender, EventArgs e)
        {
            _editor.DoRedo();
        }

        public void DoUndo(object sender, EventArgs e)
        {
            _editor.DoUndo();
        }

        public void OnSelectAll(object sender, EventArgs e)
        {
            var command = (OleMenuCommand)sender;
            command.Enabled = _editor.SupportsSelectAll;
        }

        public void OnCopy(object sender, EventArgs e)
        {
            var command = (OleMenuCommand)sender;
            command.Enabled = _editor.SupportsCopy;
        }

        public void OnCut(object sender, EventArgs e)
        {
            var command = (OleMenuCommand)sender;
            command.Enabled = _editor.SupportsCut;
        }

        public void OnPaste(object sender, EventArgs e)
        {
            var command = (OleMenuCommand)sender;
            command.Enabled = _editor.SupportsPaste;
        }

        public void OnUndo(object sender, EventArgs e)
        {
            var command = (OleMenuCommand)sender;
            command.Enabled = _editor.SupportsUndo;
        }

        public void OnRedo(object sender, EventArgs e)
        {
            var command = (OleMenuCommand)sender;
            command.Enabled = _editor.SupportsRedo;
        }

        #endregion IEditorCommonCommand
    }

 下面就是Editor的实现

    [ComVisible(true)]
    [Guid("f0cdb6b8-ed9f-4cb5-9b4d-e8afc6a177a3")]
    public class MyEditPane :EditorPaneBase<ContextEditorFactory, MyControl>
    {
        public MyEditPane()
        {
            base.Content = new MyControl();
        }
    }

这里又得说一下,那个Guid的事情,与VS交互使用的是COM,所以GUID哪些事,你懂得。

其中MyControl就是一个UserControl,就是我们编辑器的主界面了:

public partial class MyControl : UserControl ,ISimpleEditor
    {
        public MyControl()
        {
            InitializeComponent();
        }


        public bool SupportsSelectAll { get { return true; } }

        public bool SupportsCopy { get { return true; } }

        public bool SupportsCut { get { return true; } }

        public bool SupportsPaste { get { return true; } }

        public bool SupportsRedo { get { return true; } }

        public bool SupportsUndo { get { return true; } }

        public void DoSelectAll()
        {
            content.SelectAll();
        }

        public void DoCopy()
        {
            content.Copy();
        }

        public void DoCut()
        {
            content.Cut();
        }

        public void DoPaste()
        {
            content.Paste();
        }

        public void DoRedo()
        {
            content.Redo();
        }

        public void DoUndo()
        {
            content.Undo();
        }

        public bool IsDirty { get; set; }


        public void SetInstanceContext(IEditorContext context)
        {
            throw new NotImplementedException();
        }

        public bool CanSave()
        {
            throw new NotImplementedException();
        }

        public void Save()
        {
            throw new NotImplementedException();
        }

        public void SaveAs(string fileName)
        {
            throw new NotImplementedException();
        }

        public void OnSaveCompleted(string fileName)
        {
            throw new NotImplementedException();
        }

        public void OnClose()
        {
            throw new NotImplementedException();
        }

        public void LoadDocData(string fileName)
        {
            if (File.Exists(fileName))
            {
                content.Text = File.ReadAllText(fileName);
            }
        }

        private void content_TextChanged(object sender, TextChangedEventArgs e)
        {
            this.IsDirty = true;
        }
    }

前台界面,很简单,就是一个TextBox

<UserControl x:Class="Company.GL_IDE_VSPackage.MyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:vsfx="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.10.0"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             Name="MyToolWindow"
             Background="{DynamicResource {x:Static vsfx:VsBrushes.ToolWindowBackgroundKey}}">
    <Grid>
        <ScrollViewer>
            <TextBox Name="content" TextChanged="content_TextChanged" AcceptsReturn="True">
            
            </TextBox>
        </ScrollViewer>
    </Grid>
</UserControl>

 

 好吧,我承认我比较懒惰,文字不多,希望大家有所掌握。有什么么疑问的可以发留言给我,今天就到这里了。

posted @ 2013-06-24 16:43  广林  阅读(1842)  评论(2编辑  收藏  举报