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

时间:2024-01-13 08:19:44

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

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

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

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

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

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

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

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

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

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

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

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


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

IVsPersistDocData 接口 文档处理接口


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

IOleCommandTarget 接口 命令处理接口


  名称 说明
VSPackge插件系列:简单文本编辑器的实现 Exec 执行指定的命令。
VSPackge插件系列:简单文本编辑器的实现 QueryStatus 查询该对象以获得由用户界面事件生成的一个或多个命令的状态。
msdn:地址

IPersistFileFormat 接口 文件处理接口


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

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