Introduction
Many of has have wondered how Visual Studio implements nesting of items in the solution explorer of items. In case I am not explaining myself well, I am talking about the nested appearance of the three items that represent a Form
object:
- Form1.cs
- Form.Designer.cs
- Form.resx
The post from my blog can be found here.
Background
Visual Studio 2005 does not have the functionality to choose several items and create a nesting schema. In order to do this, you must open the *.csproj file with your favorite editor and then do some stuff as described here.
I would expect Visual Studio 2008 to have implemented this feature, but it doesn't. So what can you do? One solution is to use the macro from delarou's blog. The idea is fairly simple but it was the only article I found that explained the know-how of how to tell Visual Studio programmatically to nest items. I couldn't have done without this post.
But macros were not my interest because usually nested items are a product of an insertion of a templated item. So in this article I will focus on how to achieve this when creating a custom template item with multiple items. For example, a type dataset.
An article about how to create a Custom Item Template can be found here.
I will get into depth with this process because there are many articles about this on the Internet. The only thing that I have to say is that the Custom Item Template file (MyTemplate.vstemplate) that describes the template and files to use, does not allow the <DependentUpon>
attribute on each item in it.
So the only solution is to attach to the template, custom logic which is implemented in a class which implements the Microsoft.VisualStudio.TemplateWizard.IWizard
interface and tell the MyTemplate.vstemplate to use the assembly that contains the file.
The only problem for this solution is that all template logic related assemblies must be located in gac so the related projects must be signed.
The Infrastructure
After much hard testing, I came up with a solution which requires from a class only to define the logic that separate nested items from their parent. This is achieved because template logic is defined in a class that inherits from Sarafian.Templates.NestedItemTemplateBase.Base
which implements the IWizard
interface.
This class stored privately which project is the parent and which are its children. While items are added to a project and sent to the template logic to be consumed, the template in the ProjectItemFinishedGenerating
functions calls the abstract
function GetProjectItemType
and decides if it is a parent or nested.
public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
{
ProjectItemTypes type = GetProjectItemType(projectItem);
switch (type)
{
case ProjectItemTypes.Parent:
this.parentProjectItem = projectItem;
break;
case ProjectItemTypes.Child:
this.childrenProjectItems.Add(projectItem);
break;
}
}
Then when the template is finished, it adds the nested items to its parent like this:
public void RunFinished()
{
foreach (EnvDTE.ProjectItem item in this.childrenProjectItems)
{
string filename = item.get_FileNames(0);
this.parentProjectItem.ProjectItems.AddFromFile(filename);
}
}
Because the project is required to be placed in the gac, this post-build event is defined:
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe"
/if "$(TargetPath)"
Creating the Template
In the zip file above, I have implemented an example.
The template item I want to create consists of four files:
- Example.cs
- Example.Input.cs
- Example.Output.cs
- Example.Error.cs
Example.cs will be the parent and all others its children. So this is how it is done:
protected override ProjectItemTypes GetProjectItemType(EnvDTE.ProjectItem item)
{
if (item.Name.IndexOf(".Input.cs") > 0)
{
return ProjectItemTypes.Child;
}
if (item.Name.IndexOf(".Output.cs") > 0)
{
return ProjectItemTypes.Child;
}
if (item.Name.IndexOf(".Error.cs") > 0)
{
return ProjectItemTypes.Child;
}
return ProjectItemTypes.Parent;
}
It doesn't matter if they are totally different classes or partial. For practical reasons, the entire collection of files needed for the template zip file are placed in a directory called ExampleItem and are marked as Compile none and Copy if Newer in the properties of each one.
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe"
/if "$(TargetPath)"
"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe"
/l "$(TargetName)"
After the first compilation, a public key token is generated that must be set only once in the MyTemplate.vstemplate file like this:
<WizardExtension>
<Assembly>Sarafian.Templates.ExampleItemTemplate, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=6df23c9c2e2f12af</Assembly>
<FullClassName>Sarafian.Templates.ExampleItemTemplate.Template</FullClassName>
</WizardExtension>
After this, the template is ready to be used.
Points of Interest
Some things to keep in mind. Because Visual Studio uses a cached version of the gac, any change made to the binary aspect of the template requires the restart of Visual Studio that will use the template.
Debug cannot be done with traditional techniques but old techniques were used in order to understand what happens within an IWizard
implemented class. This is what I have understood.
First of all ShouldAddProjectItem
is called. If false
is returned, then the file is never templated. This means that no parameters are replaced within it. The filepath
has the value of the original file at this point.
After the replacement of custom parameters, ProjectItemFinishedGenerating
is called. At this point, the item's Name is the generated one. For example, if I wish my template to be called Sarafian
, then for the Example.Input.cs in the template, the item name is Sarafian.Input.cs.
After all files have been processed by the above function, BeforeOpeningFile
is called which tells Visual Studio to open for editing the file. In this case, the item always corresponds to Example.cs.
Finally RunFinished
is called.
The above sequence is followed when the template is Item Template and not Project Template.
More info can be found in this article by Ron Petrusha which proved very helpful for me.
History
- 25th January, 2008: Initial post
- 5th May, 2008: Added Solution for Visual Studio 2008
Future implementations will have a command line that will dynamically generate the zip file for the template. Interestingly, I cannot find the command to use Windows zip functionality. Any comments or directions would be much appreciated.