Microsoft provides a host of project template wizards for creating your initial projects. These include template wizards for creating a Console Application, a Windows Service, a Windows Form Application, a Control Library and much more. So what do these project wizards consist of? The table below illustrates the list of files included in a typical Project Wizard Template
Template Component |
Description |
.vsz File |
This file is a text file containing information on which Wizard Dispatch interface to call, and the parameters to pass to the wizard. The default wizard is VsWizard.VsWizardEngine |
.vsdir file |
A File containing a list of Wizards and where there vsz files are located. It also contains information on where to find icons for the wizard. This file is used in the initial project template selection screen. |
Html file |
File containing the UI Form of the wizard |
Script Files |
A series of Java Script files used to customize the wizards behavior |
Template file |
A file such as template.cs that contains keywords that can be substituted by the wizard to customize the code to the class, namespace, project, etc. |
Templates.inf |
A list of templates provided by the wizard |
Lets examine one of the project template wizards in detail in order to understand how they work. In an effort to learn how the template functions, we will examine the CSharpWinService Wizard. The CSharpEx.vsdir file that contains a list of CSharp template projects is located in C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\CSharpProjects and The CSharpWinService.vsz file for this project can be found there as well. Below is the line in the CSharpEx.vsdir file that contains the information about the Windows Service Wizard:
CSharpWinService.vsz|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|#2349|80|#2350|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|4556| |WindowsService
Each field in the VSDir file is delimited by the | character. The first field is required and tells the name of the visual studio wizard parameter file(or the vsz file). The next field is an optional GUID that points to a resource file for the wizard. The last GUID appearing in the file points to a dll containing an icon for the project template (this can also be a full path to a dll). The field appearing directly after the icon dll is a resource id to the icon inside the dll. The last field is a default name for the project and appears in the dialog when the project template wizard launches.
Lets take a look now at the vsz file shown below:
VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine
Param="WIZARD_NAME = CSharpWindowsServiceWiz"
Param="WIZARD_UI = FALSE"
Param="PROJECT_TYPE = CSPROJ"
This file takes the form of the old INI file (you may recall this from way back in the days of Windows 3.1). The second line indicates the program ID of the wizard engine to use. This program ID is read from the Windows registry and associated with a program with a Dispatch interface. The default wizard engine is the one shown in this file. You can use your own wizard simply by changing the dispatch program id that represents your custom dll. Well show you how to do this later in the article. The last 3 lines are parameters for the engine. Below are descriptions of some parameters in this file used by the default Wizard Engine.
Parameter for Wizard Engine |
Description |
SCRIPT_PATH |
Where the wizard's java script files are located. By default, its a subfolder directly under the WIZARD_NAME folder called Scripts. (In our example CSharpWindowsServiceWiz\Scripts\1033). The default java script is called default.js. It also uses a file called common.js for some common scripting functionality (located in \Microsoft Visual Studio .NET 2003\VC#\VC#Wizards\1033) |
TEMPLATES_PATH |
Where the wizard's template files are located. By default, its a subfolder directly under the WIZARD_NAME folder called Templates. (e.g.CSharpWindowsServiceWiz\Templates\1033) |
WIZARD_NAME |
The name of the relative Wizard Folder containing the template information. In our example the relative folder is CSharpWindowsServiceWiz and the absolute folder would be: C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\VC#Wizards\CSharpWindowsServiceWiz |
WIZARD_UI |
A Boolean indicating whether the wizard has a User interface or not. |
PROJECT_TYPE |
Indicates what programming language folder the wizard is grouped under. For Visual C# (CSPROJ) the wizard is located under C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\VC#Wizards |
Table 2 - VSWizard Parameter Options
The Java Script in the script folder called default.js extends the behavior in the common.js file and allows you to perform custom functionality for your wizard as well as respond to events thrown by the wizard. Below the default java script provided by Microsoft responds to the OnFinish event of the VSWizard (Ive added some comments to help the reader understand what is going on):
Listing 1 The OnFinish event handler from default.js
function OnFinish(selProj, selObj) // Get the Project path from the wizard // Get the Project name from the wizard var strProjectName = wizard.FindSymbol("PROJECT_NAME"); // create a safe name from the project name (this call is in var strSafeProjectName = CreateSafeName(strProjectName); // Add an additional symbol called SAFE_PROJECT_NAME and associate it wizard.AddSymbol("SAFE_PROJECT_NAME", strSafeProjectName); // Create the CSharpPRoject pasing in the Project Name, the Project var proj = CreateCSharpProject(strProjectName, strProjectPath,
var InfFile = CreateInfFile(); // Render each template and add Files specific to the CSharp Project AddFilesToCSharpProject(proj, strProjectName, strProjectPath, return e.number; } |
Upon completion, the wizard calls the OnFinish Event to fill in the templates and generate the project. You can modify OnFinish to suit the needs of your particular project.
You can also modify the template files themselves so they contain specifics for details of the files in your project. Below is a portion of a the template file file1.cs in the CSharp Windows Service Wizards template folder:
Listing 2 Part of a template for creating a class
using System; namespace [!output SAFE_NAMESPACE_NAME] // TODO: Add any initialization after the InitComponent |
The [!output SAFE_CLASS_NAME] would be replaced by the java script with a class name derived from the target name. Below is the code from the function AddFilesToCSharpProject in the common.js file that generates the class name.
Listing 2 Code from common.js that renders the template
if(!AddItemFile) var fso; |
In the code above, the target is first derived from the project name and then the class name is derived by stripping the file extension. Finally the class name is associated with the SAFE_CLASS_NAME keyword for rendering the template.
So now you get the idea of how the java script and the template works together in the default wizard. As we stated before, it's possible to bypass all of the java scripting and create the wizard completely in C#. The way to do this is simply to create our own automation object in .NET that inherits the IDTWizard dispatch interface. The IDTWizard is an interface with exactly one method, Execute. Overriding this method allows us to execute the wizard in any way you wish and passes in parameters that help us in accomplishing the task of wizarding the IDE. The steps to creating our wizard are simple. First create a simple dll library in .NET using the New->Project menu and choosing class library.
Figure 1 Choosing the project for the custom Wizard dll
In order to use the Windows Forms and EnvDTE assemblies, you will need to manually add these references. To add the references, right click on your solution in the solution explorer and choose Add Reference. Then find the references and add them to your project.
Figure 2 Choosing the references that are not already part of the library
Next, replace class1 with the following code. The class below implements the IDTWizard interface. Notice it contains a program dispatch id attribute above the class definition that allows the vsz file to recognize the wizard:
Listing 3 Class that implements the template wizard functionality
using System; namespace MyWizard1 public void Execute(object Application, int hwndOwner, ref object[] ContextParams, ref object[] CustomParams, ref EnvDTE.wizardResult retval) |
After you build the project, youll also need to register the dispatch interface using regasm.exe so that the IDE can find the wizard through the vsz file. Simply run regasm.exe on your MyWizard1.dll assembly. You could also just set the property in your project to use COM Interop as shown in figure 3:
Figure 3 - Invoking regasm through the IDE Build Property
Also, I noticed that the wizard dll assembly has to either be in the local path with the devenv.exe (the absolute path is C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\devenv.exe) or you need to make MyWizard1.dll a strong named assembly that is placed in the GAC(anotherwords a shared assembly). Otherwise the development environment has no clue where to find the wizard. (You would think it wouldnt matter since we registered it in the registry, but with .NET, what really gets registered by regasm is the mscoree.dll with the absolute name of the assembly and public key token). In the Visual Studio IDE, I changed the output path of my project to point to the IDE directory so the assembly is kept local and so I didn't have to keep copying the file all the time:
Figure 3 - Project Properties window with Output Path changed
Also the name of the wizard engine needs to be updated in the vsz file shown below:
VSWIZARD 7.0 |
As we stated before, you'll also need to list the CSharpCustomServiceWiz in the vsdir file with the other CSharp Project Templates so the IDE can find the vsz file. You don't need scripts in this case, because all of the functionality is handled by the custom engine.
Thats all there is to plugging your Wizard into the framework. Now you can add any code you wish to the Execute method to create a project. You can use the Application object parameter passed into Execute to help build your project. The Application object is the root automation object of the IDE and can be unboxed with the extensibility (_DTE) class to be used to manipulate the environment.
For example: Say we wanted to create a solution from a solution project template and save it to the disk. Also we want to provide our own wizard interface and not the one provided by the VSWizard. Inside our wizard project, we can simply create a dialog based on a Window Form and then prompt for the information our wizard needs. Then well take advantage of the Extensibility interface to create our solution.
Figure 4 - Custom Wizard User Interface Dialog
The dialog is a simple one that accepts the name of the project and the directory we wish to place the project solution. The directory input even comes with a browser button to pick a directory for our project This directory button actually invokes the SHBrowseForFolder in the shell32.dll API, but this is a topic for a completely new article, so we wont get into it here. Once weve gotten our input from the "Wizard", we can create the solution. The code for creating the solution is shown below:
Listing 4 - Extensibility code in C# to create a Project
public void CreateProject(_DTE IDEObject, string name, string destination) // Use the solution class to create a new solution MySolution.Create(destination, name); // Create the files for the solution by rendering them from an existing // Get a reference to the Visual Studio Project and // Open a template C# File, render it and add it to the project // Our own home brewed rendering routine // Add the C# file to the project // Save the project file // Save the solution file |
The code above steps create a solution by utilizing a method of the Solution class that creates files from a template and renders them from the name of the project passed into the method. It then adds a System.Xml assembly reference to the project and saves the project and the solution.
In our example, we bypass the template method provided by the wizard and created these files with our own rendering method. The way we do this is read the template into a file stream, use the replace routines of the string class to populate the template substitution values, and write the string back out to our new C# Class file. As you can see the .NET library and C# language give you a great deal of power and flexibility in how you create your wizards. Below is the code that renders the template in C#:
Listing 5 - C# Code used to render the template from a project name
public string RenderTemplate(string templateFile, string name, string destdir) // Use a stream reader to read the file into a string // Replace the template fields in the file with the appropriate values strFile = strFile.Replace("[!output SAFE_NAMESPACE_NAME]", name); // Write the string back out to the new C# class file in our solution StreamWriter sw = new StreamWriter(fs2); |
Conclusion
The extensibility object has many good features for customizing your wizard. Some ideas may be to put special menu items in the IDE specific to your project or create your own project item wizards. It essence you can create project template wizards that not only customize the project itself, but the entire Visual Studio environment!