The Wizard Helper Library
As you've seen, creating wizards isn't a very complicated task. By simply creating a COM object that implements the IDTWizard interface and placing a .vsz file on disk, you can create a wizard that the user can run by using the New Project or Add New Item dialog box. But creating and displaying the user interface for a wizard can be tedious, which is why we've avoided the topic of wizard user interfaces until now. To create the user interface for a wizard, you must create a Windows Form and the pages for the wizard. The Windows Form will display the pages of the wizard, and the Next, Back, Finish, and Cancel buttons must properly navigate between these pages.Much of the code to display the user interface for a wizard is boilerplate code and is similar for all wizards. To make creating wizards with a user interface easier, we've included in the book's sample files the source code for a library that manages this user interface. Simply called WizardLibrary, the library implements the IDTWizard interface and also handles splitting the ContextParams array into separate variables, making wizard creation less error-prone. To use this library to implement a wizard, you simply create a user control for each page of the wizard, write a small amount of code to let the library know which pages are available, and then implement wizard-specific functionality such as creating and adding project code.Let's use this library to build a wizard that generates the code for a wizarda "Wizard Wizard." First, we create a C# class library project called WizardBuilder, and then we can add a reference to the WizardLibrary assembly (you must load and build this project from the example source files first so that the library code can be referenced) and then derive the class within our project from InsideVSNet.WizardLibrary.WizardLibrary (the base class that implements the functionality for the library). After making the changes to register the object for COM, our code will look like this:
[View full width][GuidAttribute("1EF6B85C-FD5C-4fb4-BA4D-5ED221195DBF"),ProgIdAttribute("WizardBuilder.Wizard")]
public class Wizard : InsideVSNet.WizardLibrary.WizardLibrary
{
public Wizard()
{
}
}
With this basic startup code, we can define the pages that the wizard displays to the user. The first page contains two options that the user can modify: an option to create an Add New Item or New Project wizard and an option to specify where the user can run the wizard. These options take care of creating the .vsz file and placing it in the correct place for the wizard that WizardBuilder generates. The second page allows the user to specify how many pages the resulting wizard code has. Since the library uses .NET user controls to implement each page of our wizard, we can use the Add New Item dialog box to add two user controlsPage1 and Page2to our project and then add the appropriate windows controls to these forms.With the two user controls, or pages, added to our project, we need some way for the wizard library to communicate with each page to let it do the work of generating the output project and modifying the source code files that are generated. We can do this by having each page implement the interface InsideVSNet.WizardLibrary.IWizardPage, which is defined by the library and has this signature:
public interface IWizardPage
{
void PerformWork1(WizardLibrary WizardLibrary);
void PerformWork2(WizardLibrary WizardLibrary);
string HeadingLabel
{
get;
}
string DescriptionLabel
{
get;
}
System.Drawing.Image Icon
{
get;
}
void ShowHelp();
void Initialize (WizardLibrary wizardLibrary);
}
Here are the methods and properties of this interface:
PerformWork1
This method is called when the user clicks the Finish button and the wizard page should start generating code. Each page is responsible for generating its own code within the project, and that work is done within this method.
PerformWork2
Each wizard page has its PerformWork1 method called in the order that the pages are displayed. However, sometimes one page's output might depend on the output of another page. Information can be generated in the PerformWork1 method, saved, and retrieved for further processing during the PerformWork2 method. After the PerformWork1 method for each page has been called, PerformWork2 is called in the display order of each page.
HeadingLabel
This read-only property allows your wizard page to return information about the headline that's displayed in the top line of the wizard. In the Add-in Wizard's user interface, the top portion, or banner, of the user interface displays three pieces of information to the user: a headline displayed in bold text, descriptive text, and an icon for the page. This property returns the headline for the page.
DescriptionLabel
This property returns the description string to be displayed in the wizard banner.
Icon
This property returns a picture in the format of a System.Drawing.Image type to display in the banner of the wizard.
ShowHelp
This method is called when the Help button in the lower left of the wizard is clicked, signaling that the user is requesting help for the page.
Initialize
This method is called after the wizard page has been added to the list of pages maintained by the library.
With this interface implemented by each user control, we can tell the wizard library about the pages. The library implements the IDTWizard interface and its Execute method for us, but when the wizard is first run, it calls a method defined in the WizardLibrary class (from which we derived our wizard class), which is declared as abstract and is also called Execute. This method, which should be placed within the class that inherits from the WizardLibrary class, is defined as follows:
public override void Execute(EnvDTE.DTE applicationObject);
This version of Execute is where we set up the wizard library to let it know which pages are available to it. You add pages by calling the WizardLibrary.AddPage method, passing an instance of one of our user controls that implements the IWizardPage interface in the order that they should appear in the user interface for your wizard. We can create and add the two user controls we created earlier (Page1 and Page2) within the Execute method using code such as this:
public override void Execute(EnvDTE.DTE applicationObject)
{
Title = "Wizard Builder";
AddPage(new Page1());
AddPage(new Page2());
}
This code not only tells the library about the pages of our wizard but also sets the title of the wizard dialog box to Wizard Builder. The WizardLibrary class defines a property, Title, that sets the text of the user interface form for the wizard. When this Execute method returns, the wizard library has all the information it needs to run. The library then displays the Windows Form dialog box with the user control pages displayed and implements the proper navigation between pages of the wizard. The wizard library also manages the wizardResult parameter of the IDTWizard.Execute method, returning wizardResultSuccess if the Finish button is clicked, wizardResultCancel if the Cancel button is clicked, wizardResultFailure if an exception is thrown, and wizardResultBackOut if the first page of the wizard is displayed and the user clicks the Back button. With this code written, when the wizard is run, the dialog box in Figure 9-3 is shown with the first page of the wizard displayed.
Figure 9-3. The first page of the WizardBuilder sample, with the first page displayed

Wizard Variables
As we've discussed, when the IDTWizard.Execute method is called, information is passed to the wizard through the ContextParams and CustomParams arguments. But the Execute method that your wizard implements isn't passed these arguments because the wizard library handles extracting the variables from the ContextParams array and storing them as member variables. The values of CustomParams aren't extracted in the same way as ContextParams because these variables are specific to your wizard and the library has no previous knowledge about what it contains. Table 9-3 and Table 9-4 list the variable names you can use, how they correspond to the values listed in Table 9-1 and Table 9-2, and the context in which you can use them.
Variable | Corresponding Value |
---|---|
wizardType | Wizard Type |
newProjectName | Project Name |
newProjectLocation | Local Directory |
visualStudioInstallDirectory | Installation Directory |
exclusiveProject | Exclusive |
newSolutionName | Solution Name |
runSilent | Silent |
Variable | Corresponding Value |
---|---|
wizardType | Wizard Type |
projectName | Project Name |
projectItems | Project Items |
newItemLocation | New Item Location |
newItemName | New Item Name |
productInstallDirectory | Product Install Directory |
runSilent | Silent |
application
The DTE object for the instance of Visual Studio .NET in which the wizard is running
CustomArguments
A list of the custom parameters, copied verbatim from the CustomParam arguments passed to the IDTWizard.Exec method
The wizard library provides one other variable that your wizard can use. We mentioned earlier that the IWizardPage.PerformWork1 method can save information for later use in the IWizardPage.PerformWork2 method. However, one page of a wizard doesn't have access to the data of another page because they are separate objects. For storing information, the wizard library contains a data member named customData that has the type System.Collections.Specialized.ListDictionary. This data member allows one page of the wizard to store a name and value pair for use by another page of the wizard. The sample wizard we're building here uses the customData member value to store information such as the EnvDTE.Project object, which was created with the call to CreateProject within the PerformWork1 method of the first page of the wizard.
Wizard Helper Methods
The wizard library supports four helper methods that a wizard can use when generating the resulting project or file. You can use CreateProject, which has the following signature, to create a project based on a project template.
public EnvDTE.Project CreateProject(string templatePath)
This method creates a solution file if one is needed and places the project file in the correct folder on disk if the user specified creating separate folders for the project and solution files. The only parameter this project accepts is the path to the template project file. Values such as the name of the project and solution, as well as whether the solution file should be closed or the new project should be added to the currently open solution file, don't need to be passed to this method because these values are already known to the wizard. The final two methods, DeleteBetweenTokens and MakeReplacements, are C# versions of the macros of the same name shown earlier in this chapter; you can use them to modify the source files you create.
Completing the WizardBuilder Sample
Now that we've covered the techniques for building wizards using the library, we can complete the WizardBuilder sample. We've already created a class that derives from the WizardLibrary class, created the Execute method, added two pages in the form of user controls to the project, and implemented the IWizardPage interface in each of these pages. All that's left to do is to generate the output code. We'll start by creating the template files. We'll create a C# class library called WizardTemplate, specify the project setting to register as a COM object, and modify the code for the class to the following:
namespace %NAMESPACE%
{
/// <summary>
/// Summary description for Class1.
/// </summary>
[GuidAttribute("%GUID%"), ProgIdAttribute("%NAMESPACE%.Wizard")]
public class Wizard : InsideVSNet.WizardLibrary.WizardLibrary
{
public Wizard()
{
//
// TODO: Add constructor logic here
//
}
public override void Execute(EnvDTE.DTE application)
{
Title = "My Wizard";
%WIZARDPAGES%
}
}
}
When run, the wizard replaces %NAMESPACE% with the name of the project specified in the New Project dialog box, and %GUID% is replaced with a new GUID. The token %WIZARDPAGES% is replaced with code generated to add an instance of the pages to the library. The next template to create is the template for the pages of the wizard. We'll do this by running the C# Windows Control Library Wizard, modifying the generated user control to implement IWizardPage, and changing the namespace and all instances of the class name with the tokens %NAMESPACE% and %PAGENAME%, respectively. We'll copy the file for the user control into the templates folder for our wizard.The last step is to fill out the PerformWork1 and PerformWork2 methods for the two pages of the wizard. PerformWork1 for the first page handles creating the project and making replacements within the file, as outlined earlier. PerformWork2 for this first page handles building the .vsz file, placing a copy in the folder the user specified, and adding a reference to the WizardLibrary.dll assembly. PerformWork1 for the second page isn't used; PerformWork2 for this page handles adding one copy of the user control template for each of the number of pages the user specified while running the wizard, and then makes the proper replacements in the newly added page. Finally, the PerformWork2 method sets up the code in the wizard.cs file to make the replacement to the %WIZARDPAGES% token.