前阵子我的好友Sam Saffron(来自Stack Overflow和Mini Profiler)在 Skype上向我抱怨他发现了一件十分讨厌的事情,每次他在Visual Studio之外更新他的项目时,他都会遇到一个提示"重新加载项目",并会失去所有他已打开的文件,因为 Visual Studio 将关闭它们。
这显然在StackOverflow网站上成为一个话题。因为它们采用分布式的源控件,经常有十个或更多的人在同一项目内部编码,所以它们一直在集成。更新它们的项目来测试它时,所有打开的窗口就被关闭了。
我不能理解Visual Studio的此怪异行为。当你关闭 IDE时,Visual Studio 将保存所有打开的文件和窗口的位置,并当你打开你的解决方案时,VS会还原它们。 但当你打开一个项目,然后右击“卸载项目”,你就会失去所有的窗口。我已经就此报告了一个bug ,这一问题在用户心声网站上也被指出来了,在StackOverflow上也被提问到了,还有人在 twitter 上讨论此(如此多的人,毫无疑问),此问题正在折磨着一些人,然后我就想着自己修复它。这正是编写我的第一个Visual Studio插件的好机会,看看是否可能,同时也修复一个令人讨厌的东西。
下载:Workspace Reloader Visual Studio 插件 - "当你的项目文件被修改,卸载,然后重新加载时,此软件包将会重新加载打开的代码文件。
保证书:说清楚一点,这是最小的扩展。它只监听两个事件,只有 12 k,所以你没有任何理由害怕它。另外,它在我的机器上能正常运行,所以到你了。
创建一个 Visual Studio 扩展
开发 Visual Studio 扩展需要一些耐心。使用Visual Studio 2010 开发扩展变得越来越好了,但早在 2003 到2005 年,真的是很令人沮丧。你可以扩展不同类别的东西。你可以添加菜单, 添加工具条、 命令、 新的模板、新类别的设计器和可视化工具,以及使用shell来创建你自己的 IDE。
我想要创建一个无UI的插件。我不需要按钮或菜单,我只想要监听事件,并操作它们。在我读了这篇关于扩展 Visual Studio 2010的博客之后,我下载了 Visual Studio 2010 SDK。请确保下载正确的版本。因为我已经安装了 Visual Studio 2010 SP1,所以我需要更新 Visual Studio 2010 SP1 SDK.
我做了一个新的 Visual Studio 软件包。它编译成一个 VSIX文件 (这只是一个 ZIP 文件-并不是别的?)。VSIX 有一个清单 (这只是一个 XML-并不是别的?),你可以在 GUI 中编辑或作为文本文件。
我想要将我的 VSIX 包与Visual Studio 11 beta 版以及 Visual Studio 2010 一起使用,所以我添加到像这样的 SupportedProducts 节点。VSIXs 以外其他模板在Express中不受支持(虽然我一直努力):
1: <SupportedProducts> 2: <VisualStudio Version="10.0"> 3: <Edition>Ultimate</Edition> 4: <Edition>Premium</Edition> 5: <Edition>Pro</Edition> 6: </VisualStudio> 7: <VisualStudio Version="11.0"> 8: <Edition>Ultimate</Edition> 9: <Edition>Premium</Edition> 10: <Edition>Pro</Edition> 11: </VisualStudio> 12: </SupportedProducts>
我也在此文件中设置名称、 版本和说明。
我需要决定何时加载我的软件包。你可以添加一个或多个ProvideAutoLoad属性到来自VSConstants类的Package类中。很多博客帖子说你需要像这样硬编码一个GUID,但他们错了。有可用的常数。
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]
我可以在像这样的情况下自动加载软件包:
NoSolution SolutionExists SolutionHasMultipleProjects SolutionHasSingleProject SolutionBuilding SolutionExistsAndNotBuildingAndNotDebugging SolutionOrProjectUpgrading FullScreenMode
至于我的软件包,只要任何时候“存在解决方案”,我就需要加载它",所以我将使用此常量 (代替硬编码GUID):
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]
接下来,我想要监听解决方案中像卸载和加载项目的事件。我从IVsSolutionsEvents接口入手,它包括 OnBefore、 OnAfter 和 OnQuery。Elisha在StackOverflow上有一个简单的侦听器包装器,作为接听的用途。
SolutionEventsListener 使用Package.GetGlobalService 来掌握解决方案。
1: IVsSolution solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; 2: if (solution != null) 3: { 4: solution.AdviseSolutionEvents(this, out solutionEvents
Cookie); 5: }
通过使用 IVsSolutionEvents 接口,并让它们看起来像友好的事件,然后我们注册来倾听可能发生在解决方案中的事情。
1: public event Action OnAfterOpenProject; 2: public event Action OnQueryUnloadProject; 3: 4: int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) 5: { 6: OnAfterOpenProject(); 7: return VSConstants.S_OK; 8: } 9: 10: int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) 11: { 12: OnQueryUnloadProject(); 13: return VSConstants.S_OK; 14: }
我想监听卸载发生之前的事情,然后在打开项目之后执行它们。我将保存文档窗口。有一个管理Shell所调用的文档和窗口的接口, IVsUIShellDocumentWindowMgr.
我在卸载之前保存这些窗口,并在打开该项目之后重新打开它们。不幸的是这些是 COM 接口,所以我不得不在OLE. Istream中传递而不是IStream ,虽然下面的 ReopenDocumentWindows是很容易...
1: listener.OnQueryUnloadProject += () => 2: { 3: comStream = SaveDocumentWindowPositions(winmgr); 4: }; 5: listener.OnAfterOpenProject += () => 6: { 7: int hr = winmgr.ReopenDocumentWindows(comStream); 8: comStream = null; 9: };
SaveDocumentWindowPositions更加复杂,但基本上是"制作内存流,保存文档,然后寻回到流的起始点"。
1: private IStream SaveDocumentWindowPositions(IVsUIShellDocumentWindowMgr windowsMgr) 2: { 3: if (windowsMgr == null) 4: { 5: return null; 6: } 7: IStream stream; 8: NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true, out stream); 9: if (stream == null) 10: { 11: return null; 12: } 13: int hr = windowsMgr.SaveDocumentWindowPositions(0, stream); 14: if (hr != VSConstants.S_OK)