Introducing Workspace Reloader - A Visual Studio AddIn to save your open files across project reloads
A while back my buddy Sam Saffron (from Stack Overflow and Mini Profiler) complained to me on Skype that he was finding it very irritating that every time he updated his project outside of Visual Studio he would be prompted to "Reload Project" and would lose all his open files because Visual Studio would close them.
This apparently is becoming kind of an issue at Stack Overflow. Since they use distributed source control and often have a dozen or more folks all coding inside the same project they are integrating all the time. They'll be deep into something, update their project to test it and all their open windows close.
It's a weird Visual Studio behavior that I've never understood. Visual Studio saves all your open files and window positions when you close the IDE and restores them when you open your solution. But when you open a project then right click and "Unload Project" you'll lose all your windows. I've reported it as a bug and it's also been voted up at User Voice, visited as a Question at StackOverflow, and a few folks have tweeted about it (The SO guys with their thumbs on the scale, no doubt) and been bugging some folks but then I got the idea to just fix it myself. It'd be a good chance to write my first Visual Studio Add-In, see if this is even possible, and fix an irritant at the same time.
DOWNLOAD: Workspace Reloader Visual Studio Add-in - "This package will reload the code files you had open when your project file was modified and unloaded then reloaded"
Warranty: To be clear this is the smallest of extensions. It only listens to two events and it's only 12k so you have no reason that I know of to be afraid of it. Plus, it works on my machine so you've got that going for you.
Creating a Visual Studio Extension
Developing Visual Studio Extensions requires some patience. It's gotten a lot better with Visual Studio 2010 but back in the 2003-2005 days it was really hairy. There's a number of different kinds of things you can extend. You can add menus, add tool bars, commands, new templates, new kinds of designers and visualizers, as well as use just the shell to create your own IDE.
I wanted to create an add-in with Zero UI. I had no need for buttons or menus, I just wanted to listen to events and act on them. I downloaded the Visual Studio 2010 SDK after reading this blog on extending Visual Studio 2010. Make sure you get the right version. I have Visual Studio 2010 SP1 so I needed the updated Visual Studio 2010 SP1 SDK.
I made a new Visual Studio Package. This builds into a VSIX (which is just a ZIP file - isn't everything?). A VSIX has a manifest (which his just XML - isn't everything?) that you can edit in a GUI or as a text file.
I want my VSIX package to work on Visual Studio 11 Beta as well as Visual Studio 2010 so I added to the SupportedProducts node like this. VSIXs other than templates aren't supported in Express (I keep pushing, though):
<SupportedProducts>
<VisualStudio Version="10.0">
<Edition>Ultimate</Edition>
<Edition>Premium</Edition>
<Edition>Pro</Edition>
</VisualStudio>
<VisualStudio Version="11.0">
<Edition>Ultimate</Edition>
<Edition>Premium</Edition>
<Edition>Pro</Edition>
</VisualStudio>
</SupportedProducts>
I also setup the name, version and description in this file.
I need to decide when my package is going to get loaded. You can add one or more ProvideAutoLoad attributes to a Package class from the VSConstants class. A number of blogs posts say you need to hard code a GUID like this, but they are mistaken. There are constants available.
[ProvideAutoLoad("{ADFC4E64-0397-11D1-9F4E-00A0C911004F}")]
I can have my package automatically load in situations like these:
- NoSolution
- SolutionExists
- SolutionHasMultipleProjects
- SolutionHasSingleProject
- SolutionBuilding
- SolutionExistsAndNotBuildingAndNotDebugging
- SolutionOrProjectUpgrading
- FullScreenMode
For my package, I need it loaded whenever a "Solution Exists," so I'll use this Constant (in lieu of a hard coded GUID):
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]
Next, I wanted to listen to events from the Solution like the unloading and loading of Projects. I started with the IVsSolutionsEvents interface that includes OnBefore, OnAfter and OnQuery for basically everything. Elisha has a simple listener wrapper as an answer on StackOverflow that I modified.
The SolutionEventsListener uses the very useful Package.GetGlobalService to get hold of the solution.
IVsSolution solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution;
if (solution != null)
{
solution.AdviseSolutionEvents(this, out solutionEventsCookie);
}
We then sign up to hear about things that might happen to the Solution using the IVsSolutionEvents interfaces and making them look like friendly events.
public event Action OnAfterOpenProject;
public event Action OnQueryUnloadProject;
int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
{
OnAfterOpenProject();
return VSConstants.S_OK;
}
int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
{
OnQueryUnloadProject();
return VSConstants.S_OK;
}
I want to hear about things just before Unload happens and then act on them After projects Open. I'll save the Document Windows. There's an interface that manages Documents and Windows for the Shell called, confusingly enough IVsUIShellDocumentWindowMgr.
I save the windows just before the unload and reopen them just after the project opens. Unfortunately these are COM interfaces so I had to pass in not an IStream but an OLE.IStream so while the ReopenDocumentWindows is easy below...
listener.OnQueryUnloadProject += () =>
{
comStream = SaveDocumentWindowPositions(winmgr);
};
listener.OnAfterOpenProject += () => {
int hr = winmgr.ReopenDocumentWindows(comStream);
comStream = null;
};
The SaveDocumentWindowPositions is more complex, but basically "make a memory stream, save the documents, and seek back to the beginning of the stream."
private IStream SaveDocumentWindowPositions(IVsUIShellDocumentWindowMgr windowsMgr)
{
if (windowsMgr == null)
{
return null;
}
IStream stream;
NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true, out stream);
if (stream == null)
{
return null;
}
int hr = windowsMgr.SaveDocumentWindowPositions(0, stream);
if (hr != VSConstants.S_OK)
{
return null;
}
// Move to the beginning of the stream with all this COM fake number crap
LARGE_INTEGER l = new LARGE_INTEGER();
ULARGE_INTEGER[] ul = new ULARGE_INTEGER[1];
ul[0] = new ULARGE_INTEGER();
l.QuadPart = 0;
//Seek to the beginning of the stream
stream.Seek(l, 0, ul);
return stream;
}
If this does it's job you'll never know it's there. You can test it by installing Workspace Reloader, opening a project and opening a few code files. Now, edit the CSProj as a text file (maybe add a space somewhere) and save it. Visual Studio should prompt you to Reload the Project. Workspace Reloader should keep your files and windows open.
I hope this helps a few people. The source is here.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Based on my lacking knowledge, I am under impression that comStream = null does not do the trick and a Marshal.ReleaseComObject() is required. Is that true?
If you see this page, it is because an error occurred in the system while trying to process your request. We apologize for the inconvenience. This error has been reported to our team for analysis. Error Log Reference #6e83f298-a4c0-45ad-9258-7fbc87b9cfa1
Joshua - No, I don't think so. I just confirmed that here. I'm talking about a reload while the project is open. Open a project, edit the csproj and return to VS. VS closes the open documents on reload.
Aaron - I will tell them now.
Now we just need a plug in to fix the "Cannot create/shadow copy 'VariousFileName' when that file already exists." error. LOL
Just so I don’t leave you hanging, you can check out this:
http://stackoverflow.com/questions/1007200/asp-net-cannot-create-shadow-copy
I was complaining about this very thing to a co-worker a few weeks ago and he told me I should write an extension. Yay for procrastination!
I've got a sort-of related question: every so often I'll try to open a solution and VS 2010 will hang while loading Intellisense so that I have to kill it via Task Manager. The next attempt to open the same solution results in a message telling that 'Document load is being skipped' and then everything is hunky dory. Is there a way to force VS to always skip the document load phase ie: always open the solution without any opened documents?
// Move to the beginning of the stream with all this COM fake number crap
Is that one of those tricks you just know from being an experienced Windows Developer? It seems like some esoteric code.
Great icon for the extension too!
First, thanks for this excellent post.
I need to create an extension with UI but I'm unable to figure it out. I want to integrate a project with windows forms. It is basically a code generator. There are some inputs in the UI and the generated code must be added as a .cs file into the current working project.
I created a visual studio add-in project and linked the windows form by calling it in Execute(). The build was successful, but when run and click the icon in the toolbar menu, nothing happens! It does'nt even reach the breakpoint placed at the beginning of Execute()!
Your guidance will be much appreciated.
Thanks!
I faced the same issue. Your form should be inside the addin project itself. When you try to open a form in a different project(within the same solution), the form doesn't load.
Kindly help!
Comments are closed.