相比其它开发环境,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 | 在环境中初始化的编辑器。 |
IVsPersistDocData 接口 文档处理接口
名称 | 说明 | |
---|---|---|
Close | 关闭 IVsPersistDocData 对象。 | |
GetGuidEditorType | 返回创建 IVsPersistDocData 编辑对象工厂的唯一标识符。 | |
IsDocDataDirty | 确定文档是否已更改,因为次保存。 | |
IsDocDataReloadable | 确定文档是否可重新加载。 | |
LoadDocData | 从给定 MkDocument 将文档加载数据。 | |
OnRegisterDocData | 调用通过运行文档表 (RDT),则注册在 RDT 的文档数据。 | |
ReloadDocData | 重新加载文档数据,并在此过程中确定是否忽略一个后续文件更改。 | |
RenameDocData | 将文档数据重命名。 | |
SaveDocData | 将文档保存数据。 | |
SetUntitledDocPath | 设置初始名称 (或路径) 未保存的,新创建文档数据。 |
IOleCommandTarget 接口 命令处理接口
名称 | 说明 | |
---|---|---|
Exec | 执行指定的命令。 | |
QueryStatus | 查询该对象以获得由用户界面事件生成的一个或多个命令的状态。 |
IPersistFileFormat 接口 文件处理接口
名称 | 说明 | |
---|---|---|
GetClassID(Guid) | (继承自 IPersist。) | |
GetClassID(Guid) | ||
GetCurFile | 返回路径到对象的当前工作文件,或者,如果没有一种当前工作文件,对象的默认文件名提示。 | |
GetFormatList | 提供该调用方提供必要的信息委托对象打开标准常见 保存 对话框 (使用 GetSaveFileNameViaDlg 函数)。 | |
InitNew | 在没有权限的状态指示对象初始化自身。 | |
IsDirty | 确定对象是否以保存更改为其当前文件。 | |
Load | 打开已指定的文件并初始化从文件内容的对象。 | |
Save | 将该对象的副本保存到指定文件。 | |
SaveCompleted | 通知对象可以推断保存事务,并且对象可以写入它的文件。 |
[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>
好吧,我承认我比较懒惰,文字不多,希望大家有所掌握。有什么么疑问的可以发留言给我,今天就到这里了。