Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Tucking ViewModels Beneath Views in Solution Explorer in Visual Studio 2013

0.00/5 (No votes)
31 Oct 2013 1  
Tucking ViewModels Beneath Views in Solution Explorer in Visual Studio 2013.

VMTuckedAway

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.

ExtensionManager

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:

ToolsMenu

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.

AddCommandToToolBar

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.

ToolBarSetup

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”.

ToolBarSetupWithText

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.

NewVSPackage

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;
}

/// <summary>
/// This function is the callback used to execute a command when the a menu item is clicked.
/// See the Initialize method to see how the menu item is associated to this function using
/// the OleMenuCommandService service and the MenuCommand class.
/// </summary>
void MenuItemCallback(Object sender, EventArgs e) {
    try {
        //get access to the familar DTE object.
        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;

            //need to figure out which select items are the view and viewmodel
            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;
            }

            //line of code that creates the dependency between files.
            viewProjectItem.ProjectItems.AddFromFile(viewModelProjectItem.FileNames[1]);

            //show the tucked into viewmodel
            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, // false
            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. 

IncludeInVSIX

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here