Back when I was using Visual Studio 2010 a few weeks ago I had a cool macro that would allow me to select a view and viewmodel file in the Solution Explorer, run the macro and then the viewmodel would be nested nicely under the view as pictured above.
Well… Beginning with Visual Studio 2012 (which I skipped over), Visual Studio no longer support macros. Kind of a bummer since many developers wrote macros and depended on them for automating tasks, etc.
The good news is, writing a Visual Studio 2013 VSPackage is not really that difficult and the DTE macro code can pretty much be copied and pasted and with a little editing you’re up and running again. With the VSPackage you also get the benefit of not watching that spinning and dancing macro icon that would occasionally appear in the Windows Taskbar which caused your macro to freeze for awhile.
Background
I like to tuck my viewmodels beneath the view. To me, it makes for nice organization and condenses the number of top level files I have to view in the Solution Explorer.
Most of my viewmodels have a one-to-one relationship with its view. For those that don’t, I don’t tuck them beneath.
I also like to name my views and viewmodels according to the above pattern. For me, it keeps things clean and consistent.
Introducing Visual Studio 2013 Extension View Model Tuck Away
The below image was captured from the “Extensions and Updates…” dialog after I installed the VSPackage.
Operation
The digitally signed package available on the Visual Studio Gallery operates like this:
- Developer selects a view and viewmodel file in the solution explorer
- Developer runs the ViewModel Tuck Away command
The VSPackage will verify that:
- Two files have been selected in the Solution Explorer
- That one file has a .xaml file extension and contains the word “viewmodel”
- That one file has a .cs file extension and contains the word “view”
The VSPackage will then tuck the selected viewmodel beneath the selected view.
During the execution of the VSPackage command, the Visual Studio Status Bar text will be updated to reflect the success or failure of the command.
I wrote the VSPackage this way to enforce naming standards at my work and home projects.
I know that some (many) may not like this. No problem, the source is on GitHub (see Source below) and you can very easily modify it, rebuild the solution and double click on your new VSIX file.
Surfacing the VSPackage Command in Visual Studio 2013
You can surface the VSPackage Command in three ways:
- Tools Menu, ViewModel Tuck Away
- Add command to a Toolbar
- Provide shortcut key for command (not really practical since you used the mouse to select the two files, may as well click a Toolbar icon or text.)
By default the command is added to the Tools Menu as seen below:
To add the command to a Toolbar follow the numbers below. #2 below allows you to select the Toolbar you want the command to appear on. #7 allows you to position the command on the selected Toolbar.
I added my command to the Standard Toolbar and moved it to the right side as pictured below. For me, that little icon will do.
However you may want to see the commands text as pictured below. To do this, use the above “Modify Selection” ComboBox just below #7 above and select “Image and Text”.
What is a VSPackage and How Did You Do This?
You can read all about Visual Studio 2013 VSPackages here. According to this MSDN article:
“VSPackages are software modules that extend the Visual Studio integrated development environment (IDE) by providing UI elements, services, projects, editors, and designers.”
To author your own VSPackage you’ll need to download and install the VS 2013 SDK from here, look towards the bottom of the page in the “Additional Software” section for Visual Studio SDK. The documentation for the SDK is here.
After installing the SDK you’ll get some new templates (very good I might add). To fire up a new VSPackage follow the numbers.
I strongly suggest studying the default VSPackage that the template creates. I adds a Tools menu command that displays a message box. Simple, but there is a lot of plumbing code it creates for you that you pretty much don’t have to fool with. The VSPackage only has a few files of “plumbing” making it easy to understand how it all fit together.
The below code is the core functionality required to identity two selected items, figure out which is the view and viewmodel and then set the dependency.
Of interest is how much power VSPackages have access to. For example the line of code: var dte = (DTE)GetService(typeof(DTE));
provides access to the DTE. Most macro programmers are already familiar with the power and features exposed by the DTE. Once you command is invoked, you fire up the DTE and code away. Not a bad upgrade path from macros.
In a similar fashion this line of code: var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
provides the developer access to the IVsUIShell capabilities.
As I mentioned above, you can use your own custom logic for determining which selected file is the parent and which will be the new child of the parent. There are many techniques for determining this, like I said I chose to enforce naming standards for my views and viewmodels.
Boolean IsView(String targetFileName) {
var extension = Path.GetExtension(targetFileName);
return !String.IsNullOrWhiteSpace(extension) && extension == ".xaml" &&
targetFileName.IndexOf("view") > -1;
}
Boolean IsViewModel(String targetFileName) {
var extension = Path.GetExtension(targetFileName);
return !String.IsNullOrWhiteSpace(extension) && extension == ".cs" &&
targetFileName.IndexOf("viewmodel") > -1;
}
void MenuItemCallback(Object sender, EventArgs e) {
try {
var dte = (DTE)GetService(typeof(DTE));
if (dte.SelectedItems.Count == 2) {
String fileNameOne = dte.SelectedItems.Item(1).ProjectItem.FileNames[1].ToLower();
String fileNameTwo = dte.SelectedItems.Item(2).ProjectItem.FileNames[1].ToLower();
ProjectItem viewProjectItem;
ProjectItem viewModelProjectItem;
if (IsView(fileNameOne) && IsViewModel(fileNameTwo)) {
viewProjectItem = dte.SelectedItems.Item(1).ProjectItem;
viewModelProjectItem = dte.SelectedItems.Item(2).ProjectItem;
} else if (IsView(fileNameTwo) && IsViewModel(fileNameOne)) {
viewProjectItem = dte.SelectedItems.Item(2).ProjectItem;
viewModelProjectItem = dte.SelectedItems.Item(1).ProjectItem;
} else {
dte.StatusBar.Text = "Must select a view.xaml file and a viewmodel.cs " +
"file in the solution explorer.";
return;
}
viewProjectItem.ProjectItems.AddFromFile(viewModelProjectItem.FileNames[1]);
viewProjectItem.ExpandView();
dte.StatusBar.Text = "Your viewmodel is now dependent on its view.";
} else {
dte.StatusBar.Text = "Must select a view.xaml file and a viewmodel.cs file in " +
"the solution explorer.";
}
} catch (Exception ex) {
var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
var clsId = Guid.Empty;
Int32 result;
var message = String.Format("Exception occurred in VmTuckAway: {0}", ex);
ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox(
0,
ref clsId,
"VmTuckAway Exception",
message,
String.Empty,
0,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
OLEMSGICON.OLEMSGICON_INFO,
0, out result));
}
}
VSIX Manifest Files and VSPackage Assets
If you want to make your own VSPackages you’ll also want to understand the, “source.extension.vsixmanifest” file and how to properly edit it.
You need to read the VSIX Extension Schema 2.0 Reference found here. Very easy read with good examples.
One tip I remembered the hard way was the file property “Include in VSIX” which must be True for VSPackage assets you include in the “source.extension.vsixmanifest.”
These Large and Small icons provide a nice UI for your VSPackage in the “Extensions and updates…” dialog.
GitHub Source
The source code can be downloaded from GitHub here:
Download Signed VSPackage Extension on Visual Studio Gallery
Close
Hopes this helps someone and have a great day,
Just a grain of sand on the worlds beaches.