Click here to Skip to main content
15,881,089 members
Articles / Programming Languages / C#

Extending Visual Studio Part 3 - Item Templates

Rate me:
Please Sign up or sign in to vote.
4.94/5 (40 votes)
14 Apr 2012CPOL7 min read 106.1K   1.2K   86   39
Learn how to extend Visual Studio 2010 by creating custom Item Templates.

Extending Visual Studio  

This article is part of the series 'Extending Visual Studio'.

Part 1 - Creating Code Snippets
Part 2 - Creating Addins
Part 3 - Item Templates   

Introduction  

In part three of my series on Extending Visual Studio, we're going to take a look at Item Templates. Item Templates are what you see when you choose the menu option 'Add New Item':

Image 1

Here we can see a stack of Item Templates. Some come with Visual Studio, some are my own. It's surprisingly straightforward to add your own custom Item Templates, but the documentation on this is a little scant. In this article we'll put together a band new Item Template for a 'View Model' - a class that implements INotifyPropertyChanged

Hold Your Fire! 

This article describes how to create Item Templates - don't worry that the template we create isn't enormously functional - it's for illustrative purposes only! Please avoid picking the View Model to death - it's just to give us a better task that 'Hello World'! For those of you who are interested, I have created some much more functional Item Templates for MVVM systems, more on that later. 

Before We Begin  

You will need the Microsoft Visual Studio SDK. I'm using Microsoft Visual Studio 2010 SP1, be aware that there are different installers for 2010 and 2010 SP1. Grab the SDK from the links below:

Microsoft Visual Studio 2010 SDK http://www.microsoft.com/download/en/details.aspx?id=2680
Microsoft Visual Studio 2010 SP1 SDK http://www.microsoft.com/download/en/details.aspx?id=21835 

Once you've installed the SDK you'll have a whole host of new Item Templates and Project Templates to play with - filed under 'Visual C# > Extensibility':  

Image 2

To get started, open Visual Studio, choose 'New Project' and select 'C# Item Template' Call the project 'ViewModelItemTemplate'. 

Anatomy of an Item Template 

Here's a screenshot of how the project will look. There's very little to it.

Image 3 

ViewModelItemTemplate.ico - The icon for the item template.
ViewModelItemTemplate.vstemplate - The template metadata.
Class.cs - The file the template will use as a base. 

Firstly let's rename 'Class.cs' to 'ViewModel.cs'. Now we'll open the vstemplate file and update the metadata:

XML
<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" Type="Item" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>    
    <Name>View Model</Name>
    <Description>Creates a class suitable for use as a View Model, which implements INotifyPropertyChanged.</Description>
    <Icon>ViewModelItemTemplate.ico</Icon>
    <TemplateID>341358cd-cacb-4f5f-9e5d-be7b3f8a4a97</TemplateID>
    <ProjectType>CSharp</ProjectType>
    <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion>
    <NumberOfParentCategoriesToRollUp>1</NumberOfParentCategoriesToRollUp>
    <DefaultName>ViewModel.cs</DefaultName>
  </TemplateData>
  <TemplateContent>
    <References>
      <Reference>
        <Assembly>System</Assembly>
      </Reference>
    </References>
    <ProjectItem ReplaceParameters="true">ViewModel.cs</ProjectItem>    
  </TemplateContent>
</VSTemplate> 

Blow by blow, here's what we're defining with the metadata (the TemplateData item):

  • The name displayed to the user is 'View Model'.
  • A description for the user, displayed on the right of the screen when they select the template.
  • The icon to use. 
  • A GUID for the template.
  • The project type (CSharp, VisualBasic or Web).
  • The required framework version is the .NET Framework 2.0.
  • The number of parents that will show this item, so if the item is in
    Category 1 > Category 2 > Category 3 > Category 4 > Item
    and this is 2, then it'll be shown when you child on Category 2, Category 3 and Category 4. 
  • The default name - this is what is shown in the file name box. 
The TemplateContent is a bit more straightfoward in this example. We state that 'System' is an assembly that a reference is required to for this item, and that we're going to take the item 'ViewModel.cs' from this project and replace parameters in it (replacements we'll take a look at in a bit). 

Building the Template Itself 

Building the template itself is dead easy - you just use a few of the predefined tokens with dollar signs around them. Here's what the ViewModel.cs template looks like: 

C#
using System;
using System.Collections.Generic;
$if$ ($targetframeworkversion$ >= 3.5)using System.Linq;
$endif$using System.Text;
using System.ComponentModel;

namespace $rootnamespace$
{
    /// <summary>
    /// The $safeitemrootname$ class.
    /// </summary>
    public class $safeitemrootname$ : INotifyPropertyChanged
    {
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Notifies the property changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        private void NotifyPropertyChanged(string propertyName)
        {
            //  Get and fire the event.
            var theEvent = PropertyChanged;
            if (theEvent != null)
                theEvent(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Sheesh it's a bit ugly with those odd looking tokens everywhere but you can see what's happening - $rootnamespace$ is (you guessed it) the root namespace of the destination project, $safeitemroot$ is the file name they've chosen, with things like spaces removed. 

That's all there is to it! We have the item template ready to go. The next thing to do is to create a VSIX package which will contain the template. We can also use this as a way to test and debug it.

Creating the VSIX Package

In the solution, add a new project called 'ViewModelTemplateVsix' with the type as VSIX project.

Image 4 

Double click on the 'source.extension.vsixmanifest' file. Here you can edit data about the package itself. We'll set a better title and some descriptions, as below:

Image 5 

Now we can press 'Add Content' to include our Item Template:

Image 6

Now that we've done this make sure the VSIX package is the startup project and hit F5. This will start the Experimental Instance of Visual Studio which is where we can test and debug the extension. Create a new C# Class Library project and then choose 'Add New Item...' our new ViewModel item template is listed - choose 'Example' as the file name and add the item. 

Image 7

Now this is most impressive - we have got a new class with the correct name that implements INotifyPropertyChanged! Here's the newly generated class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace ClassLibrary2
{
  /// <summary>
  /// The Example class.
  /// </summary>
  public class Example : INotifyPropertyChanged
  {
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notifies the property changed.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    private void NotifyPropertyChanged(string propertyName)
    {
      //  Get and fire the event.
      var theEvent = PropertyChanged;
      if (theEvent != null)
        theEvent(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

Congratulations! At this stage you now know how to build a basic item template. If this is all you need then you're good to go. In the next part of this article we'll look at how we can include a Wizard for the Item Template that'll let us configure some options about how to create it.

Creating a Wizard for the Template

In many cases, if you're creating an Item Template it's for something a bit more specific and specialised. You may not just want to create a new item with a certain layout, but also allow the user to specify some options. In this case you need to include a Wizard for the Item Template. 'Wizard' is a bit of a misleading name perhaps, all we are in fact going to do is show a single dialog with some options.

I use WPF for my Wizards nowadays but to keep things simple we'll use WinForms. Add a new Windows Forms Control Library to the project, and call it 'ViewModelWizard'. 

Image 8 

Add a new form to the library and call it NewViewModelForm. We're going to allow the user to optionally include a notifying property with a specified type and name. Lay out the form as below:

Image 9

I'm not going to go into the details of how the form works, it's very simple - it just exposes the 'Include Example' as a boolean property as well as the Example Name and Example Type as strings. We now need to actually create the Wizard class. Add a new class to the project called 'ViewModelWizard'. At this stage also add references to:

  • ENVDTE 
  • Microsoft.VisualStudio.TemplateWizardInterface

As below:

Image 10

The Wizard class must implement 'IWizard'. Right click on the 'IWizard' line and choose 'Implement Interface'. You're class should now look like this:

C#
public class ViewModelWizard : IWizard
    {
        public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
        {
            throw new NotImplementedException();
        }

        public void ProjectFinishedGenerating(EnvDTE.Project project)
        {
            throw new NotImplementedException();
        }

        public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
        {
            throw new NotImplementedException();
        }

        public void RunFinished()
        {
            throw new NotImplementedException();
        }

        public void RunStarted(object automationObject, 
            Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
        {
            throw new NotImplementedException();
        }

        public bool ShouldAddProjectItem(string filePath)
        {
            throw new NotImplementedException();
        }
    }

The only function we care about is 'RunStarted' - in this function we'll show the form and add the values specified to the 'replacementsDictionary'. This will let us use the tokens in our template. Update the class to look like this:

C#
public class ViewModelWizard : IWizard
    {
        public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
        {
        }

        public void ProjectFinishedGenerating(EnvDTE.Project project)
        {
        }

        public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
        {
        }

        public void RunFinished()
        {
        }

        public void RunStarted(object automationObject, 
            Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
        {
            //  Create the form.
            var form = new NewViewModelForm();

            //  Show the form.
            form.ShowDialog();

            //  Add the options to the replacementsDictionary.
            replacementsDictionary.Add("$IncludeExample$", form.IncludeExample ? "1" : "0");
            replacementsDictionary.Add("$PropertyName$", form.PropertyName);
            replacementsDictionary.Add("$PropertyType$", form.PropertyType);
        }

        public bool ShouldAddProjectItem(string filePath)
        {
            return true;
        }
    }

You can see that all we're doing is showing the form and adding the options into the replacements dictionary. The dictionary can only contain strings - so use "1" or "0" for booleans!

Now we can update the actual template (ViewModel.cs) itself, to use these tokens. We'll set up a conditional block based on $IncludeExample$ that will add a property of name $PropertyName$ and type $PropertyType$ that uses NotifyPropertyChanged. Update ViewModel.cs to the below:

C#
using System;
using System.Collections.Generic;
$if$ ($targetframeworkversion$ >= 3.5)using System.Linq;
$endif$using System.Text;
using System.ComponentModel;

namespace $rootnamespace$
{
    /// <summary>
    /// The $safeitemrootname$ class.
    /// </summary>
    public class $safeitemrootname$ : INotifyPropertyChanged
    {
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Notifies the property changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        private void NotifyPropertyChanged(string propertyName)
        {
            //  Get and fire the event.
            var theEvent = PropertyChanged;
            if (theEvent != null)
                theEvent(this, new PropertyChangedEventArgs(propertyName));
        } $if$ ($IncludeExample$ == 1)

        /// <summary>
        /// The value of $PropertyName$.
        /// </summary>
        private $PropertyType$ $PropertyName$Value;

        /// <summary>
        /// Gets or sets $PropertyName$.
        /// </summary>
        public $PropertyType$ $PropertyName$
        {
            get { return $PropertyName$; }
            set
            {
                if( $PropertyName$Value != value)
                {
                    $PropertyName$Value = value;
                    NotifyPropertyChanged($PropertyName$);
                }
            }
        } $endif$
    }
}

Before we can test this we need to do three things - sign the Wizard assembly, install the Wizard assembly in the GAC, then update the Item Template to use the Wizard.

To sign the assembly, open the Wizard project settings, choose Signing, check 'Sign the Assembly' and enter a new key - no password is necessary.

To install the assembly into the GAC, add the following to the post build steps of the Wizard project:

32 bit Machine:
"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil" /if "$(TargetPath)"
"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\sn" -T "$(TargetPath)"

64 bit Machine:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil" /if "$(TargetPath)"
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\sn" -T "$(TargetPath)"

This just build the project. The post build step will install the wizard to the GAC and show the strong name token:

Assembly successfully added to the cache
  
  Microsoft (R) .NET Framework Strong Name Utility  Version 3.5.30729.1
  Copyright (c) Microsoft Corporation.  All rights reserved.
  
  Public key token is e0700af0fcefc5c7

We'll need this token to do the final part of the project. Open the ViewModelItemTemplate.vsitemtemplate file and after the closing tag of TemplateContent add:

XML
<!-- Use the appropriate IWizard from the ViewModelWizard assembly. -->
  <WizardExtension>
    <Assembly>
      ViewModelWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e0700af0fcefc5c7
    </Assembly>
    <FullClassName>ViewModelWizard.ViewModelWizard</FullClassName>
  </WizardExtension>

Don't forget to use the correct public key token (highlighted in bold).

Now press F5. Add a new View Model to a class library - we get the user interface we created when we add it and can specify an example property:

Image 11

and we get the result below:

C#
using System.ComponentModel;

namespace ClassLibrary2
{
  /// <summary>
  /// The Example class.
  /// </summary>
  public class Example : INotifyPropertyChanged
  {
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notifies the property changed.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    private void NotifyPropertyChanged(string propertyName)
    {
      //  Get and fire the event.
      var theEvent = PropertyChanged;
      if (theEvent != null)
        theEvent(this, new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    /// The value of FirstName.
    /// </summary>
    private string FirstNameValue;

    /// <summary>
    /// Gets or sets FirstName.
    /// </summary>
    public string FirstName
    {
      get { return FirstName; }
      set
      {
        if (FirstNameValue != value)
        {
          FirstNameValue = value;
          NotifyPropertyChanged(FirstName);
        }
      }
    }
  }
}

Final Thoughts 

I hope you've enjoyed this article - please feel free to comment on ideas, problems or potentially improvements!  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

Comments and Discussions

 
GeneralGroovy Pin
osborned@hotmail.com26-Feb-15 5:03
osborned@hotmail.com26-Feb-15 5:03 
QuestionSigning into the GAC Pin
Vladimir Saleh13-Oct-14 5:06
Vladimir Saleh13-Oct-14 5:06 
AnswerRe: Signing into the GAC Pin
Dave Kerr13-Oct-14 21:37
mentorDave Kerr13-Oct-14 21:37 
GeneralRe: Signing into the GAC Pin
Vladimir Saleh14-Oct-14 4:46
Vladimir Saleh14-Oct-14 4:46 
GeneralRe: Signing into the GAC Pin
Dave Kerr19-Oct-14 19:28
mentorDave Kerr19-Oct-14 19:28 
GeneralRe: Signing into the GAC Pin
Vladimir Saleh20-Oct-14 23:30
Vladimir Saleh20-Oct-14 23:30 
Questionok I have two big questions: Pin
ali rez13-Sep-13 22:29
ali rez13-Sep-13 22:29 
QuestionWPF Example Pin
Member 1004350031-Aug-13 12:10
Member 1004350031-Aug-13 12:10 
AnswerRe: WPF Example Pin
Dave Kerr31-Aug-13 21:15
mentorDave Kerr31-Aug-13 21:15 
Questionniko neznanovic ulica Zafrkanovic bb Pin
Armin Kasibovic13-Mar-13 9:57
Armin Kasibovic13-Mar-13 9:57 
Questionniko neznanovic ulica Zafrkanovic bb Pin
Armin Kasibovic13-Mar-13 9:54
Armin Kasibovic13-Mar-13 9:54 
AnswerRe: niko neznanovic ulica Zafrkanovic bb Pin
Armin Kasibovic13-Mar-13 9:55
Armin Kasibovic13-Mar-13 9:55 
GeneralRe: niko neznanovic ulica Zafrkanovic bb Pin
Jibesh13-Mar-13 10:03
professionalJibesh13-Mar-13 10:03 
Questionperfect one i was luking fr....but there is always a BUT :D Pin
user-599974212-Mar-13 6:23
user-599974212-Mar-13 6:23 
AnswerRe: perfect one i was luking fr....but there is always a BUT :D Pin
Dave Kerr14-Mar-13 1:10
mentorDave Kerr14-Mar-13 1:10 
GeneralRe: perfect one i was luking fr....but there is always a BUT :D Pin
johannesnestler22-Apr-15 2:01
johannesnestler22-Apr-15 2:01 
GeneralMy vote of 5 Pin
DontSailBackwards22-Feb-13 19:56
DontSailBackwards22-Feb-13 19:56 
GeneralRe: My vote of 5 Pin
Dave Kerr22-Feb-13 21:32
mentorDave Kerr22-Feb-13 21:32 
GeneralMy vote of 5 Pin
John B Oliver25-Sep-12 0:16
John B Oliver25-Sep-12 0:16 
GeneralRe: My vote of 5 Pin
Dave Kerr25-Sep-12 0:55
mentorDave Kerr25-Sep-12 0:55 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA12-May-12 18:30
professionalȘtefan-Mihai MOGA12-May-12 18:30 
GeneralRe: My vote of 5 Pin
Dave Kerr24-Sep-12 0:48
mentorDave Kerr24-Sep-12 0:48 
GeneralMy vote of 5 Pin
Sergio Andrés Gutiérrez Rojas26-Apr-12 18:33
Sergio Andrés Gutiérrez Rojas26-Apr-12 18:33 
Good work!!
GeneralRe: My vote of 5 Pin
Dave Kerr24-Sep-12 0:48
mentorDave Kerr24-Sep-12 0:48 
QuestionFrom one Visual Studio extender to another Pin
Pete O'Hanlon25-Apr-12 21:44
mvePete O'Hanlon25-Apr-12 21:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.