Inside the “Default Browser Switcher” extension
You may have already heard of the recently WoVS extension “Default Browser Switcher” (download from Visual Studio Gallery or from Extension Manager inside VS) that helps in choosing what browser you want to launch from VS while debugging your ASP.NET applications.
You can start by reading the original post from Scott Hanselman where he describes some interesting inner workings of VS related to how it handles the launching of the VS default browser. Note this is the default browser to be launched from VS which may or may not be your default system-wide browser.
If you’re brave enough to survive Scott’s post then you’re ready to enter some even more obscure details: how to package that into an extension for Visual Studio…
We started by defining the set of commands we wanted to support and we ended up with ten commands total. That is a “Set default Browser to X” command multiplied by Internet Explorer, Firefox, Chrome, Safari and Opera and a “View in Browser X” command multiple by the same number of browsers. The first set would go into a VS Toolbar and the second set would go into a sub menu whose parent would be a new “View in Browser” command.
To author the UI we used the very handy and friendly VSPackage Builder which allowed us to quickly design our 10 commands and place them where we wanted inside VS:
(Yes, that’s a DSL for authoring your own custom UI inside VS!)
An interesting bit is that we’ve to make sure the TextChanges flag was set on each command as we wanted the ability to dynamically change the command’s original text at runtime:
We do this so we can show different tooltips at runtime for the same command. We show the “Set X as the default browser” when that browser happens to not be the default one already:
And then we show “X is the default browser” when you hover over the current default one:
From the coding side all what’s needed is to properly set the Text property of the given MenuCommand to whatever text you want. In our case, it looks like this:
command.Text = IsDefaultBrowser ? String.Format("{0} is the default browser", browserName) : String.Format("Set {0} as the default browser", browserName);
We also added a new “View in Browser” command which we wanted to sit next to the built-in one so we had to use a submenu which ended up looking like this:
This was produced using a model like the following:
Where we have a group of commands belonging to our menu which is then placed on different built-in groups, like in the File top-level menu and in the text editor’s context menu.
Besides our fancy UI we also have some black magic code, like the one used to retrieve the currently selected item:
Start by retrieving the selection service:
var selService = GetService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
And call its GetCurrentSelection method which happens to have a lovely signature:
int GetCurrentSelection( out IntPtr ppHier, out uint pitemid, out IVsMultiItemSelect ppMIS, out IntPtr ppSC )
Assuming you’ve a previous unmanaged C++ background it is really easy to figure out what each one of these parameters mean <g>. Just in case you don’t, here is the cheat sheet: pointers and pointers to pointers.
ppHier – returns the currently selected hierarchy (or null if it happen to be more than one selected)
pitemid – pointer to the itemid of the selected item (this is just a cookie you can else on another 500 methods to return meaninful information)
These are the two parameters we’re most interested in. if pitemid doesn’t return VSITEMID_SELECTION it means there isn’t a multiple selection, so we can safely grab ppHier as being our target selected item.
Once we have that guy identified we can add a bit more magic to get to an instance of an automation project which will allows us to better examine what the current project is about:
ppHier.GetProperty((uint)Microsoft.VisualStudio.VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_ExtObject, out obj);
Actually before doing so I need to convert my pointer (ppHier IntPtr) to an IVsHierarchy, piece of cake:
var hier = Marshal.GetTypedObjectForIUnknown(ppHier, typeof(IVsHierarchy)) as IVsHierarchy;
And now yes, ask for the VSHPROPID_ExtObject:
var hier = Marshal.GetTypedObjectForIUnknown(ppHier, typeof(IVsHierarchy)) as IVsHierarchy;
You: Victor, are we there yet???
Me: Almost.
Now let’s release the COM interface we don’t want to keep handing around:
Marshal.Release(ppHier);
And cast the hierarchy to an EnvDTE.Project type:
var project = hier as EnvDTE.Project;
Just to drop the last bit of magic which will cause a property named Object to return to us a VSProject type:
var vsproject = project.Object as VSLangProj.VSProj;
I could keep going and going but I’m guessing you had enough for now!
We can only hope that all this code gets simplified in the next version of VS by using MEF and nicely designed APIs.
Hope you enjoy this extension!