Share via


Writing a Custom Wizard Page

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies.
This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

In most cases, the wizard pages that are automatically constructed by the Wizard Framework, based on the <GatheringServiceData> element, will prove sufficient for your needs. However, in some cases, you might want to create a custom wizard page, using a class that implements the page. In this case, instead of describing page fields in the configuration file, you refer to the code that specifies the custom page, by providing its type.

To write a custom wizard page, you must do the following:

  1. Create a new class that inherits from CustomWizardPage and implements your Windows Form.
  2. For each recipe argument that you are going to collect, declare a property with the language attribute RecipeArgument and a setter, as shown in the following code example.
[RecipeArgument]
public <TypeOfArgument> <ArgumentName>
{
  set 
  {
     < Custom code to update the UI >
  }
}
  1. Fill the implementation of the setter code with code that will update the user interface.
  2. Use the IDictionaryService to set the value of the recipe argument when you need to update it.
  3. Update the Guidance Package configuration file to declare the type and size of the page, as shown in the following XML code example.
<GatheringServiceData>
  <Wizard xmlns="https://schemas.microsoft.com/pag/gax-wizards" SchemaVersion="1.0">
    <Pages>
      <Page Type="gp5.WizardPages.CustomPage1, gp5.WizardPages " Width="800" Height="500"/>
      </Page>
    </Pages>
  </Wizard>
</GatheringServiceData>

The CustomWizardPage class is provided by the Recipe Framework and inherits from the abstract class WizardPage. The following code example shows a simple custom page that collects one string argument named Argument1.

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Practices.WizardFramework;
using System.ComponentModel.Design;

namespace gp5.CustomWizardPages
{
  /// <summary>
  /// Example of a class that is a custom wizard page
  /// </summary>
  public partial class CustomWizPage1 : CustomWizardPage
  {
    public CustomWizPage1(WizardForm parent) : base(parent)
    {
    // This call is required by the Windows Form Designer.
    InitializeComponent();
    // TODO: Add any initialization after the InitializeComponent call
    }

    [RecipeArgument]
    public string Argument1
    {
      set
      {
        if (value != null)
        {
          this.textBox1.Text = value.ToString();
        }
        else
        {
          this.textBox1.Text = "";
        }
      }
    }

    private void textBox1_TextChanged(object sender, EventArgs e)
    {
      IDictionaryService dictionaryService = GetService(typeof(IDictionaryService)) as IDictionaryService;
      if (string.IsNullOrEmpty(textBox1.Text.ToString()))
        dictionaryService.SetValue("Argument1", null);
      else
        dictionaryService.SetValue("Argument1", textBox1.Text.ToString());
    }
  }
}

The text box textBox1 and the label label1 shown on the form are defined in the Designer partial class associated with the form, as shown in the following code example.

namespace gp5.CustomWizardPages
{
  partial class CustomWizPage1 : CustomWizardPage
  {
    /// <summary> 
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region Designer generated code
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
      ...
      // 
      // CustomWizPage1
      // 
      this.Controls.Add(this.textBox1);
      this.Controls.Add(this.label1);
      this.Headline = "First Headline";
      this.InfoRTBoxSize = new System.Drawing.Size(550, 50);
      this.InfoRTBoxText = "You can change the size of this info-box and put text in it by manipulating Info" +
      "RTBox element of the related Designer class";
      this.Name = "CustomWizPage1";
      this.ShowInfoPanel = true;
      this.Size = new System.Drawing.Size(796, 451);
      this.Skippable = true;
      this.StepTitle = "First Custom Step";
      this.Controls.SetChildIndex(this.label1, 0);
      this.Controls.SetChildIndex(this.textBox1, 0);
      ((System.ComponentModel.ISupportInitialize)(this)).EndInit();
      this.ResumeLayout(false);
      this.PerformLayout();
      ...
    }
    #endregion

    public System.Windows.Forms.TextBox textBox1;
    private System.Windows.Forms.Label label1;
  }
}

Every time an argument changes value, such as when its value provider recalculates the value, the framework calls the setter of the property named after the argument (Argument1 in the example). On the other hand, every time the user changes the argument value on the form (textBox1 in the example), the developer needs to call the IDictionaryService to update the value of the argument in the dictionary.

The framework keeps track of the required arguments on the page and enables the Next/Finish button when all of them acquire values.

One drawback of using custom pages is that you need to handle input errors yourself. For example, if you need to declare an integer argument, you must ensure that the value collected for this argument on the page is an integer, before you pass it to the IDictionaryService. The following code example declares the Argument2 property as an integer. If the value of the corresponding control textBox1 is not an integer, the value of the property does not update the corresponding value of the argument, and the background color of textBox1 changes.

[RecipeArgument]
public int Argument2
{
  set
  {
    if ((value == 0))
    {
      this.textBox1.Text = "";
    }
    else
    {
      this.textBox1.Text = value.ToString();
    }
  }
}

private void textBox1_TextChanged(object sender, EventArgs e)
{
  int boxValue;
  IDictionaryService dictionaryService = GetService(typeof(IDictionaryService)) as IDictionaryService;
  if (string.IsNullOrEmpty(textBox1.Text))
  {
    dictionaryService.SetValue("Argument2", null);
  }
  else
  if (int.TryParse(textBox1.Text, out boxValue))
  {
    dictionaryService.SetValue("Argument2", boxValue);
    this.textBox1.BackColor = System.Drawing.Color.Green;
  }
  else
  {
    this.textBox1.Text = "";
    this.textBox1.BackColor = System.Drawing.Color.Purple; 
  }
}

See also

Developing Wizards | Defining a Wizard | Defining a User Interface Value Editor | Defining a User Interface Value Converter | Implementing Argument Value Propagation

Developing Wizards | Defining a Wizard | Defining a User Interface Value Editor | Writing Type Converters | Implementing Argument Value Propagation