Introduction
Wizards have to be one of the more popular approaches to walking a user through a process. I know I appreciate them. But writing them has always struck me as involving a lot of boiler-plate code. And trying to structure things to make the individual pages reusable gets pretty complicated pretty quickly.
The wizard framework is part of a growing collection of utilities that I've made available in a series of assemblies. Here's how you can download the latest version of the software:
The product page for the library is located at http://www.jumpforjoysoftware.com/product/5. You might be interested in looking at what else I've made available on the site, at http://www.jumpforjoysoftware.com/.
There are a couple of wizards built with the framework contained in the library collection. One is Olbert.Utilities.WPF.DatabaseSelector, which as you might imagine, is a wizard for selecting a SQL Server database. Another is Olbert.Utilities.nHydrate.Installer.nHydrateDatabaseSelector, which extends DatabaseSelector for use in selecting SQL Server databases configured to work with the nHydrate data modeling environment.
If you haven't checked out nHydrate, you should (http://nhydrate.codeplex.com/; there are also a number of articles about nHydrate available on CodeProject). It's a cool tool!
Background
The concept of a wizard sounds like a workflow to me, so I spent quite some time reading up on Windows Workflow, figuring there must be some nifty support tools for crafting wizards buried in it. There probably are, but I wasn't able to find the kind of simple framework which handled the plumbing of what I wanted to do in a wizard while I focused on defining the "what". So in the end, I decided to roll my own. But fair warning: the framework I'm describing here could be totally obsolete even before it's published.
As I was developing the wizard framework, I ran into some interesting limitations in the current version of XAML. Because the kind of wizards I usually write work by modifying a state variable (i.e., as the wizard progresses, the state of the wizard gets more and more defined) and state variables are very solution-dependent, it would be convenient to create a Generics-based wizard-hosting window. That's not supported all too well in WPF 4, although apparently it will be supported in the future.
But for now, that means my design had to give up strong typing of the wizard's state. For similar reasons, I also had to give up strong typing of the wizard hosting window. As a result, the plumbing routines that handle state management behind the scenes do a lot of Type checking, because the code can't be sure that you haven't passed it a SqlServerConfigurationState
object when it was expecting a MyFavoriteVideoGameState
object. Among other things, this caused me to gain a new appreciation for the value of strong typing :).
Another facet of WPF programming which I was aware of, but had to really come to terms with in building and using the wizard framework, involves the distinction between functional inheritance and visual inheritance. It would be really convenient to derive a window class from another window class and have all of the UI elements in the base class be carried over into the new, derived class. Unfortunately, that's not possible in WPF today, and probably not in the future, either, I suspect. If you try to do that today, you'll get a nice error message from the compiler reminding you that you can't derive the UI portion of one window from another window class which itself has a UI defined for it.
But you can work around the limitation pretty easily, at the expense of some redundancy in your coding. You just need to remember that while the XAML file for a window and the code-behind file for a window are intimately related, they are not inextricably linked. They each define a different aspect of what it means to be that particular kind of window. The XAML file describes what the window looks like, while the code-behind describes how the window functions. Granted, implementing that functionality often requires referencing components in the UI -- think about getting the text contained in a TextBox
-- but you don't have to do that by directly referencing the control in question. You can do it indirectly, by defining virtual methods or properties. So instead of writing something like this:
string myTextBoxValue = tbxMyTextBox.Text;
you can write it like this:
protected virtual string MyTextBoxValue
{
get { return null; }
}
string myTextBoxValue = MyTextBoxValue;
Granted, you now have to write an override to "link" the MyTextBoxValue
to a particular control in the UI of the derived class. But you've gained the ability to reuse the functionality of your base window class "inside" multiple UIs.
In practice, this approach would get awfully tedious and annoying if there were a lot of links between your window's functionality and its UI. In that case, I bet it would be easier to think about writing a code-generation template. But if there aren't too many links between the UI and the functionality, it's a practical approach.
Using the code
In the case of the wizard framework, there's only one link:
public virtual Frame PageFrame
{
get { return null; }
}
public virtual object WizardState
{
get { return null; }
protected set { }
} ]]>
Your derived UI must contain a Frame
element for hosting the individual wizard pages, because each page must derive from the custom WizardPage
class defined in the library. The PageFrame
property makes that element accessible to the code in the WizardWindow
class. The WizardState
property, while not a link to a UI element, must also be defined in your derived wizard-hosting window class. It gives the plumbing code access to whatever object you're using to hold the wizard's state information.
How it works: Configuring the wizard hosting window
You build a wizard using the framework by making a bunch of declarations about each page of the wizard in a window you derive from a special Window
class, WizardWindow
. WizardWindow
provides the plumbing for the wizard, as well as gives you opportunities to affect the page flow (e.g., by letting you cancel the user's transition to the next page of the wizard).
Defining the XAML for your wizard-hosting window is done by simply adding a new Window
class to your project and then modifying it to derive from WizardWindow
. This involves adding some stuff to the window's XAML file and editing the code-behind. Here's what a modified XAML file looks like:
<![CDATA[]]>
The important things to recognize here are that you have to define a namespace that refers to the assembly where WizardWindow
is defined, which you use as the opening declaration of your window's UI definition. In the example, I've given that required namespace the prefix "maowiz".
You might be confused by the opening declaration referring to WizardWindow
rather than your custom window's class. In reality, the declaration refers to both. You can read that first line as "create a window UI whose functionality derives from the WizardWindow
class and link it to the functionality defined in the SimpleWizard
class defined in the current assembly".
Naturally, the local class you're linking your UI to must be derived from the same class that declares the "starting point" for the XAML definition. If you don't, you'll get a compiler error message reminding you that the UI's base class must be the same as the code-behind's base class. The required modification to the code-behind to achieve this is very simple. You just replace the base class name in the class declaration line:
<![CDATA[namespace MyWizardApp
{
public partial class SimpleWizard : WizardWindow
{]]>
By the way, when making these changes, it's not uncommon to have to compile the project twice to get everything "fixed up" correctly. On occasions, I've even had to do a Rebuild All. This isn't surprising given all the housekeeping Visual Studio has to do behind the scenes, but it can be a bit disconcerting the first time you get some wonderfully unhelpful error message like "Unknown build error, required file could not be found".
You'll also need to override WizardWindow
's PageFrame
and WizardState
properties to point to the objects you're using in your window's UI to hold the wizard pages (which must be a Frame
element) and your wizard's state.
<![CDATA[
public override Frame PageFrame
{
get { return frmWizard; }
}
public override object WizardState { get; protected set; }]]>
Since you'll want the wizard to be able to switch pages, you need to hook up WizardWindow
's navigation methods to your UI's navigation elements:
<![CDATA[
private void btnCancel_Click( object sender, RoutedEventArgs e )
{
OnWizardCanceled();
}
private void btnPrevious_Click( object sender, RoutedEventArgs e )
{
MoveToPreviousPage();
}
private void btnNext_Click( object sender, RoutedEventArgs e )
{
MoveToNextPage();
}]]>
Finally, you need to overload WizardWindow
's OnWizardLoaded()
method to start up the wizard:
<![CDATA[
protected override void OnWizardLoaded()
{
base.OnWizardLoaded();
PageNumber = 0;
}]]>
Because all page shifts are initiated by changing WizardWindow
's PageNumber
property, you start the wizard by setting that property to the page number of whatever page you want to show first. This isn't done for you in the plumbing code because sometimes you don't want to start a wizard on the first page (e.g., it might be an introductory page that you want to give experienced users a chance to skip).
There are a host of other methods you can override and events you can respond to defined in the WizardWindow
class. One of those, OnWizardCompleted()
, may be of particular interest, since it's called when the wizard is successfully completed, giving you an opportunity to do something with whatever the user just defined. You don't always need to override it. In fact, if all your wizard is doing is defining a particular state variable, you could display it modally and then extract the final state information after ShowDialog()
returns successfully.
One final point about declaring the wizard hosting window: RegularPageNextText
and LastPageNextText
are dependency properties of WizardWindow
which let you define the text displayed by your wizard in the Next button. Setting them in your XAML window declaration isn't enough, by itself, for the values you specify to show up in the wizard when it runs. You have to also bind WizardWindow
's CurrentNextText
property to the content of the "Next" button on your window's UI:
<![CDATA[
<Button Name="btnNext"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="3"
IsEnabled="{Binding ElementName=TheWindow, Path=CanMoveNext}"
Content="{Binding ElementName=TheWindow,
Path=CurrentNextText, NotifyOnSourceUpdated=True}"
Click="btnNext_Click" />]]>
This can't be done in code because WizardWindow
, not being tied to any particular UI, doesn't "know" which UI element corresponds to the "Next" button (actually, as I write this, it occurs to me this could be done by WizardWindow
if it defined a virtual property that you'd link to a particular UI element by overriding the property in your derived class; one more for the to-do list...).
The above code fragment also shows another optional but strongly advisable to use feature of WizardWindow
: controlling the ability of the user to move by enabling or disabling the wizard navigation buttons. In the example, this is being done by binding the "Next" button's IsEnabled
property to WizardWindow
's CanMoveNext
property. CanMoveNext
and its counterparts CanMovePrevious
and CanCancel
can be set by either your derived wizard-hosting window or individual wizard pages. But that will only work if you wire up the functionality.
By the way, the binding expressions in the example use the name of the wizard-hosting window (not being particularly clever, I called it "TheWindow
"). This is a critical optional property of a window (or control, for that matter) declaration. There are other ways of giving the .NET Framework the information it needs to "find" the window at runtime, but the easiest is simply to name the window and use that.
You may wonder why you have to identify a window to be able to access its properties when the property access is taking place inside the definition of the window itself. I admit I find that pretty confusing myself. I think the reason has something to do with the fact that the XAML file "puts a face on" the code-behind file, but isn't itself part of the entity defined by the code-behind. In any event, it's easy enough to do once you get the hang of it.
How it works: Data flow
Before getting into the details of adding WizardPage
s to the wizard, it's worth talking about how data moves through the wizard as it runs. For each page displayed, the following sequence of activities occurs:
- If we're already displaying a wizard page and we're moving forward to a later page:
- retrieve the values of the bound properties on the current
WizardPage
- give the wizard codebase a chance to modify the bound properties
- give the wizard codebase a chance to validate the modified property collection; if validation fails, cancel the page transition
- if validation succeeds, update the state object to reflect the modified page property values
- give the application codebase one last chance to cancel the transition
- If we're already displaying a wizard page but we're moving backwards to an earlier page, don't bother processing the current page's values; just proceed with the transition
- If we're displaying the first wizard page, just proceed with the transition
- If we're moving off of a wizard page, give the "expiring" page a chance to do any necessary cleanup (e.g., of resources it may have consumed)
- Create an instance of the new, soon-to-be-displayed
WizardPage
-derived class
- Retrieve the values of all properties in the state object bound to a property on the new
WizardPage
from the state object
- Give the wizard codebase a chance to modify the retrieved bound values
- Assign the modified state values to the
WizardPage
properties to which they are bound
- Display the new wizard page and give its codebase a chance to do any final initialization
- Allow user interaction with the
WizardPage
- A page transition triggered by the user occurs
- And repeat until there are no more wizard pages to display
These steps are handled by various protected virtual methods in the WizardWindow
class. Many of those methods also raise corresponding events which you can respond to from other code if your "wizard" is actually running within another application.
How it works: Custom wizard page resources
At this point, your wizard won't do much of anything because you haven't defined any wizard pages for it to process. This is done declaratively in your wizard hosting window's XAML file.
<![CDATA[]]>
The first step in declaring the pages to be used in the wizard involves declaring resources that some of the page declarations will use. If you don't need those elements of a page declaration, you won't need to define anything in the resources section. But you probably will, as they give you the ability to modify data as it flows between your state object and individual pages, and to do validation of user input after a page is completed but before the values are posted back to the state object.
There are three "access points" in the data flow, each of which you tap into by deriving a class from a base class and then instantiating that derived class as a resource:
Modifying state data before binding it to a WizardPage: StateValuesProcessor
A key concept in the wizard framework is that of binding some resource or property to a property exposed by a WizardPage
. Binding takes place twice, once when a WizardPage
is initialized for display by the wizard-hosting window, and once when the WizardPage
is "completed", signified by the user initiating navigation to the next page. The wizard framework gives you an opportunity to modify what will get bound to the WizardPage
in that initial binding by defining and referencing a class derived from StateValuesProcessor
.
StateValuesProcessor
contains just one method, which you override in a derived class to define the functionality you want:
<![CDATA[
public virtual PropertyBindingValues ModifyStateValues( PropertyBindingValues values,
object state, WizardWindow wizWindow )
{
return values;
}]]>
By default, ModifyStateValues
simply returns the PropertyBindingValues
collection it was given. But you can modify that collection before returning it in your derived class. You might want to do this, for example, in order to define a "virtual property" that doesn't exist in your state object. Consider the following example:
<![CDATA[
public class DatabaseStateProcessor : StateValuesProcessor
{
private static HashSet<SqlServerPermissions> reqdPermissions =
new HashSet<SqlServerPermissions>(
new SqlServerPermissions[] {
SqlServerPermissions.CreateTable,
SqlServerPermissions.CreateView,
SqlServerPermissions.CreateFunction,
SqlServerPermissions.CreateProcedure });
public override PropertyBindingValues ModifyStateValues(
PropertyBindingValues values, object state, WizardWindow wizWindow )
{
PropertyBindingValues retVal = base.ModifyStateValues(values, state, wizWindow);
PropertyBinder newBinding = new PropertyBinder()
{
PageProperty = "RequiredPermissions",
PageType = typeof(DatabasePage),
StateType = typeof(SqlConfigInfo)
};
values.Add(new PropertyBindingValue(newBinding, reqdPermissions, true));
return retVal;
}
}]]>
Here we are adding a "virtual property" which will be bound to a property called RequiredPermissions
on the wizard page to which this derived class relates. RequiredPermissions
could've been made a property of the state object used by the wizard. But I chose not to because I wanted to keep the state object "focused" on defining the elements of a SQL connection string.
What this also demonstrates is that a wizard's entire state is defined by more than just its state object. The entire state also encompasses the wizard-hosting window itself (which is why that window is passed to ModifyStateValues
as an argument) as well as any "externals" that you want to include, like the static field reqdPermissions
.
You may wonder how the derived class DatabaseStateProcessor
gets associated with a particular wizard page and wizard window. The answer is simple: you declare the association when you declare a wizard page in the hosting window's XAML file. You'll see how that's done a little later.
Validating page data before binding it to the wizard's state: WizardPageValidator
A key concept in the wizard framework is that of binding some resource or property to a property exposed by a WizardPage
. Binding takes place twice, once when a WizardPage
is initialized for display by the wizard-hosting window, and once when the WizardPage
is "completed", signified by the user initiating navigation to the next page. The wizard framework gives you an opportunity to validate the page data before binding it back to the state object, which lets you cancel the update (and the transition to the next page). You do this by defining and referencing a class derived from WizardPageValidator
.
WizardPageValidator
contains just one method, which you override in a derived class to define the functionality you want:
<![CDATA[
public abstract PageBindingResult Validate( PropertyBindingValues propValues,
object state, WizardWindow wizWindow );]]>
You implement your validation logic by defining a Validate
method in your derived class. Consider the following example:
<![CDATA[
public class DatabaseValidator : WizardPageValidator
{
public override PageBindingResult Validate(
PropertyBindingValues propValues, object state, WizardWindow wizWindow )
{
SqlConfigInfo config = ( (SqlConfigInfo) state ).Copy();
PageBindingResult result = propValues.GetValueForPage("Database");
if( !result.IsValid ) return result;
config.Database = (string) ( (PageBindingValue) result ).Value;
switch( DatabaseImage.IsAccessible(config.GetConnectionString(), config.Database) )
{
case DatabaseImage.DatabaseAccessibility.Accessible:
return new PageBindingValue();
case DatabaseImage.DatabaseAccessibility.DoesNotExist:
SqlServerImage server = new SqlServerImage(config.GetConnectionString());
if( server.CanCreate ) return new PageBindingValue();
else return PageBindingOutcome.InvalidValue.ToError(
"database doesn't exist and credentials don't support database creation");
default:
return PageBindingOutcome.InvalidValue.ToError(
"the database cannot be accessed with the specified user credentials");
}
}
}]]>
Here we are checking the database name returned by a particular wizard page to see if, indeed, it references a database we can access using server and credential information the wizard gathered in an earlier step. That earlier information is contained in the state object passed to the Validate()
method.
You may wonder how the derived class DatabaseValidator
gets associated with a particular wizard page and wizard window. The answer is simple: you declare the association when you declare a wizard page in the hosting window's XAML file. You'll see how that's done a little later.
Modifying page data before binding it to the wizard's state: PageValuesProcessor
A key concept in the wizard framework is that of binding some resource or property to a property exposed by a WizardPage
. Binding takes place twice, once when a WizardPage
is initialized for display by the wizard-hosting window, and once when the WizardPage
is "completed", signified by the user initiating navigation to the next page. The wizard framework gives you an opportunity to modify the page data after it's validated but before binding it back to the state object. You do this by defining and referencing a class derived from PageValuesProcessor
.
PageValuesProcessor
contains just one method, which you override in a derived class to define the functionality you want:
<![CDATA[
public virtual PropertyBindingValues ModifyPageValues(
PropertyBindingValues values, object state, WizardWindow wizWindow )
{
return values;
}]]>
You implement your modification logic by making changes to the PropertyBindingValues
collection passed to the method. By default, the base implementation simply returns that collection, unchanged. I don't have an example of using this functionality handy, but it's pretty similar to what was described for the StateValuesProcessor
example, except that you have to specify the StateProperty
name rather than the PageProperty
name when you add a "virtual property" to the collection. That is because you are changing a property on the state object, not on a WizardPage
object.
How it works: Wizard page declarations
Once you've declared whatever custom resources your wizard pages will need, you need to declare the wizard pages that make up the wizard. You do this by adding entries to the PageBindings
collection exposed by the WizardWindow
class:
<![CDATA[
<maowiz:WizardWindow x:Class="MyWizardApp.SimpleWizard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maowiz="clr-namespace:Olbert.Utilities.WPF;
assembly=Olbert.Utilities.WPF.Wizard">
xmlns:local="clr-namespace:Olbert.Utilities.WPF"
x:Name="TheWindow"
RegularPageNextText="Next"
LastPageNextText="Finish"
Height="300" Width="300">
<Window.Resources>
<local:DatabaseStateProcessor x:Key="DatabasePreProcessor" />
<local:SummaryStateProcessor x:Key="SummaryPreProcessor" />
<local:CredentialsValidator x:Key="CredentialsValidator" />
<local:DatabaseValidator x:Key="DatabaseValidator" />
<local:NullableBooleanToBooleanConverter x:Key="NullableBooleanToBooleanConverter" />
</Window.Resources>
<maowiz:WizardWindow.PageBinders>
<maowiz:PageBinder Title="Introduction"
PageType="local:IntroPage" SourcePath="db_selector/pages/IntroPage.xaml">
<maowiz:ResourceBinder PageProperty="Document" StateProperty="IntroDoc" />
</maowiz:PageBinder>
<maowiz:PageBinder Title="Choose Server"
PageType="local:ServerPage" SourcePath="db_selector/pages/ServerPage.xaml">
<maowiz:PropertyBinder PageProperty="Server" />
<maowiz:ResourceBinder PageProperty="MoreInfo" StateProperty="MoreServerInfo" />
</maowiz:PageBinder>
<maowiz:PageBinder Title="Credentials"
PageType="local:CredentialsPage"
SourcePath="db_selector/pages/CredentialsPage.xaml"
Validator="{StaticResource CredentialsValidator}">
<maowiz:PropertyBinder PageProperty="UserID" />
<maowiz:PropertyBinder PageProperty="Password" />
<maowiz:PropertyBinder PageProperty="IntegratedSecurity"
Converter="{StaticResource NullableBooleanToBooleanConverter}" />
</maowiz:PageBinder>
<maowiz:PageBinder Title="Choose Database"
PageType="local:DatabasePage" SourcePath="db_selector/pages/DatabasePage.xaml"
StateValuesProcessor="{StaticResource DatabasePreProcessor}"
Validator="{StaticResource DatabaseValidator}">
<maowiz:PropertyBinder PageProperty="Database" />
<maowiz:ResourceBinder PageProperty="MoreInfo" StateProperty="MoreDatabaseInfo" />
</maowiz:PageBinder>
<maowiz:PageBinder Title="Action Summary" PageType="local:SummaryPage"
SourcePath="db_selector/pages/SummaryPage.xaml"
StateValuesProcessor="{StaticResource SummaryPreProcessor}">
<maowiz:PropertyBinder PageProperty="Server" />
<maowiz:PropertyBinder PageProperty="Database" />
</maowiz:PageBinder>
</maowiz:WizardWindow.PageBinders>
</maowiz:WizardWindow>]]>
The syntax may look a little odd, but it's straightforward once you remember that the first declaration defines the specific property (i.e., the PageBinders
collection) of the WizardWindow
object to which you are adding entries. Each of those entries, in turn, describes an instance of PageBinder
that you want the XAML processor to create for you and add to the collection when it creates the window. Because both the PageBinders
collection and the PageBinder
class are part of the framework, you need to prefix references to them with the namespace prefix which will let the XAML processor figure out the specific class to which your directives are referring.
Each PageBinder
directive can have various properties assigned to it, and can also have individual ResourceBinder
or PropertyBinder
directives added "within" it. That's because the PageBinder
class is itself a collection of ResourceBinder
objects (PropertyBinder
s derive from ResourceBinder
s and so can be added to a collection of ResourceBinder
objects).
While the help file contains detailed information about each of the PageBinder
properties, here's a summary table describing what they are and what they do:
Property |
What it does |
Comments |
PageType |
Sets the Type of WizardPage you're adding to the wizard. |
Required. |
Title |
Sets a title for the WizardPage which can be accessed and displayed in a navigation control. |
Optional, but desirable. |
SourcePath |
The path to the XAML file for the page in the Visual Studio project that defines it. |
Required... and you must get it right. The syntax looks like a file path declaration which starts at the "root" of the Visual Studio project, but does not contain a leading '/'. The wizard framework requires that the page's XAML interface file be defined in the same assembly which defines the page's class. That's equivalent to saying the code-behind and XAML files for a WizardPage must be in the same assembly, and "linked" to each other the way Visual Studio links them when it creates a new Page object. Frankly, this may be a requirement imposed by Visual Studio itself, although I don't know that for sure. What I do know is that I've never put the code-behind file for a XAML file any place else :). |
StateValuesProcessor |
Sets the object you're using to modify the values retrieved from the state object before binding them to the WizardPage . The value will be a StaticResource reference to a previously-defined instance in the wizard's "resource environment". Typically that will be a resource entry in the window's resource declaration section. |
Optional. If you don't define one, the values will be retrieved and bound to the page "as is". Note that the value you assign is actually a reference to a static resource you previously defined in the window's resource section (or in the application resource file, if you're using one). |
PageValuesProcessor |
Sets the object you're using to modify the values retrieved from the page before binding them to the state object. The value will be a StaticResource reference to a previously-defined instance in the wizard's "resource environment". Typically, that will be a resource entry in the window's resource declaration section. |
Optional. If you don't define one, the values will be retrieved and bound to the state "as is". Note that the value you assign is actually a reference to a static resource you previously defined in the window's resource section (or in the application resource file, if you're using one). |
Validator |
Sets the object you're using to validate the (possibly modified) values retrieved from the page before binding them to the state object. The value will be a StaticResource reference to a previously-defined instance in the wizard's "resource environment". Typically that will be a resource entry in the window's resource declaration section. |
Optional. If you don't define one, the values will be assumed valid. Note that the value you assign is actually a reference to a static resource you previously defined in the window's resource section (or in the application resource file, if you're using one). |
Of course, a PageBinder
that doesn't contain any ResourceBinder
or PropertyBinder
declarations would make for a pretty limited wizard, one where no page could display or manipulate any part of the wizard's state. Resource/PropertyBinder
declarations define which properties of a WizardPage
are "tied" to which properties of the state object, or which object in the wizard's "resource environment".
ResourceBinder
s are currently "read only" in that they can be bound to wizard page properties, but changing the values in a wizard page will not result in the value of the resource object being changed. That's just a design limitation at this point, and could change in the future.
Again, while there is more complete documentation on each of the properties of a ResourceBinder
or PropertyBinder
declaration, here's a summary table describing what they are and what they do (all of these properties exist in both the ResourceBinder
and PropertyBinder
classes):
Property |
What it does |
Comments |
PageProperty |
Sets the name of the property on the wizard page that's involved in the binding. |
Required, sort of; you have to define at least one of either PageProperty or StateProperty . You can define both. This flexibility is intended to reduce typing when PageProperty and StateProperty have the same value, which is often. Defining either one and not the other will cause both of them to have the same value. Cannot be set to blank or empty. |
StateProperty |
Sets the name of the property on the state object that's involved in the binding. |
Required, sort of; you have to define at least one of either PageProperty or StateProperty . You can define both. This flexibility is intended to reduce typing when PageProperty and StateProperty have the same value, which is often. Defining either one and not the other will cause both of them to have the same value. Cannot be set to blank or empty. |
Converter |
Sets the converter class to be used to convert the value between different Types when it is being assigned to the page or the state object. The value must be a previously-defined StaticResource , and the underlying class must implement IValueConverter . The IValueConverter.Convert() method is called when the value is being assigned to the wizard page. The IValueConverter.ConvertBack() method is called when the value is being assigned to the state object. |
Optional. If nothing is assigned, the value being passed back and forth is assumed to be compatible with both the page's property and the state's property. |
ConverterParameter |
Defines a string parameter passed to the IValueConverter.Convert() and IValueConverter.ConvertBack() methods if and when they are called. |
Optional. |
History
- February 28, 2011: 0.5.1 (Initial release).