Click here to Skip to main content
15,895,799 members
Articles / Desktop Programming / WPF

A (Mostly) Declarative Framework for Building Simple WPF-based Wizards

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
7 Mar 2011LGPL322 min read 19.3K   229   15  
A declarative framework for building WPF wizards.
/*
* Olbert.Utilities.WPF
* utilities for simplifying the use of nHydrate entities in WPF applications
* Copyright (C) 2011  Mark A. Olbert
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published 
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows;

namespace Olbert.Utilities.WPF
{
    /// <summary>
    /// Defines a class, dervied from ObservableCollection&lt;ResourceBinder&gt;, which defines all of
    /// the various property bindings for a particular WizardPage and various properties of a WizardPage
    /// </summary>
    public class PageBinder : ObservableCollection<ResourceBinder>
    {
        private WizardWindow wizard;
        private Type pageType;
        private ConstructorInfo pageCtor;
        private Uri pageUri;

        /// <summary>
        /// Creates a new instance
        /// </summary>
        public PageBinder()
        {
        }

        /// <summary>
        /// Creates a new instance which is a copy of the supplied instance
        /// </summary>
        /// <param name="toCopy">the instance to duplicate</param>
        protected PageBinder( PageBinder toCopy )
        {
            wizard = toCopy.wizard;
            pageType = toCopy.pageType;
            pageCtor = toCopy.pageCtor;
            Title = toCopy.Title;
            pageUri = toCopy.pageUri;

            foreach( ResourceBinder propBinder in toCopy )
            {
                Add(propBinder.Copy());
            }

            SourcePath = toCopy.SourcePath;
            Validator = toCopy.Validator;
            StateValuesProcessor = toCopy.StateValuesProcessor;
            PageValuesProcessor = toCopy.PageValuesProcessor;
        }

        /// <summary>
        /// Returns a new object which is a copy of the current instance
        /// </summary>
        /// <returns>a new object which is a copy of the current instance</returns>
        public virtual PageBinder Copy()
        {
            return new PageBinder(this);
        }

        /// <summary>
        /// Gets or sets the WizardWindow that is hosting the binding collection
        /// </summary>
        public WizardWindow Wizard
        {
            get { return wizard; }

            set
            {
                wizard = value;

                foreach( ResourceBinder resBinder in this )
                {
                    resBinder.Wizard = wizard;
                }
            }
        }

        /// <summary>
        /// Gets or sets the title for the WizardPage
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Gets or sets the path to the XAML file defining the WizardPage being described by this
        /// instance. This is defined based on the "project-relative" path within the Visual Studio
        /// solution explorer window.
        /// </summary>
        public string SourcePath { get; set; }

        /// <summary>
        /// Creates a new instance of the WizardPage described by this instance
        /// </summary>
        /// <returns></returns>
        public WizardPage CreatePage()
        {
            if( pageCtor == null ) return null;

            return (WizardPage) pageCtor.Invoke(null);
        }

        /// <summary>
        /// Gets or sets the Uri for locating the compiled XAML file defining the WizardPage described
        /// by this instance
        /// </summary>
        public Uri PageUri
        {
            get
            {
                if( PageType == null ) return null;

                if( pageUri == null )
                {
                    // the xaml is assumed to be in the same assembly that defines the WizardPage class
                    Assembly pageAss = Assembly.GetAssembly(PageType);
                    string[] nameParts = pageAss.FullName.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);

                    pageUri = new Uri(String.Format("/{0};component/{1}", nameParts[0], SourcePath), UriKind.RelativeOrAbsolute);
                }

                return pageUri;
            }
        }

        #region Page side of binding 

        /// <summary>
        /// Gets or sets the Type of the WizardPage being described by this instance
        /// </summary>
        public Type PageType
        {
            get { return pageType; }

            set
            {
                pageType = value;

                if( pageType == null ) pageCtor = null;
                else pageCtor = pageType.GetConstructor(Type.EmptyTypes);

                foreach( ResourceBinder wizBinder in this )
                {
                    wizBinder.PageType = pageType;
                }
            }
        }

        /// <summary>
        /// Gets the currently displayed WizardPage, which may or may not be of the same Type as
        /// the WizardPage described by this instance
        /// </summary>
        protected WizardPage CurrentPage
        {
            get
            {
                if( wizard == null ) return null;
                if( wizard.PageFrame == null ) return null;

                return wizard.PageFrame.Content as WizardPage;
            }
        }

        /// <summary>
        /// Gets or sets the processor class used to modify property values retrieved from the 
        /// WizardPage object before they are used to set property values on the state object
        /// </summary>
        public PageValuesProcessor PageValuesProcessor { get; set; }

        /// <summary>
        /// Retrieves and modifies the property values of bound WizardPage properties
        /// </summary>
        /// <returns>a collection of bound WizardPage property values</returns>
        public PropertyBindingValues GetPageValues()
        {
            if( PageType == null ) return null;
            if( Wizard == null ) return null;
            if( CurrentPage == null ) return null;

            if( !CurrentPage.GetType().IsOrIsDerivedFrom(PageType) )
                return null;

            PropertyBindingValues propValues = new PropertyBindingValues(Wizard);

            foreach( ResourceBinder curBinding in this )
            {
                PageBindingResult result = curBinding.GetPageValue();

                PageBindingValue value = result as PageBindingValue;

                propValues.Add(new PropertyBindingValue(curBinding, ( value == null ) ? null : value.Value, result.IsValid));
            }

            PageValuesProcessor processor = ( PageValuesProcessor == null ) ? new PageValuesProcessor() : PageValuesProcessor;

            return processor.ModifyPageValues(propValues, State, Wizard);
        }

        /// <summary>
        /// Gets or sets a custom validator to be used with the WizardPage being described by this
        /// instance
        /// </summary>
        public WizardPageValidator Validator { get; set; }

        /// <summary>
        /// Validates a collection of WizardPage bound property values
        /// </summary>
        /// <param name="values">the WizrdPage bound property values to be validated</param>
        /// <returns>a ValidationResult describing the results of the validation process</returns>
        public PageBindingResult ValidatePageValues( PropertyBindingValues values )
        {
            if( Validator == null ) return new PageBindingValue();

            if( values == null )
                return PageBindingOutcome.UndefinedObject.ToError("the supplied PropertyBindingValues object is undefined");

            if( State == null )
                return PageBindingOutcome.UndefinedObject.ToError("the wizard state object was undefined");
            if( StateType == null )
                return PageBindingOutcome.UndefinedObject.ToError("StateType is undefined");

            if( Wizard == null )
                return PageBindingOutcome.UndefinedObject.ToError("the WizardWindow object was undefined");

            return Validator.Validate(values, State, Wizard);
        }

        #endregion

        #region State side of binding

        /// <summary>
        /// Gets the Type of the primary state object. Returns null if the Wizard property is not set
        /// or if the WizardWindow's WizardState property is undefined
        /// for the 
        /// </summary>
        public Type StateType
        {
            get
            {
                if( wizard == null ) return null;
                if( wizard.WizardState == null ) return null;

                return wizard.WizardState.GetType();
            }
        }

        /// <summary>
        /// Gets the object holding the wizard's state. Returns null if the Wizard property is not set.
        /// </summary>
        protected object State
        {
            get
            {
                if( wizard == null ) return null;

                return wizard.WizardState;
            }
        }

        /// <summary>
        /// Gets or sets the processor class used to modify property values retrieved from the 
        /// state object before they are used to set property values on the WizardPage object
        /// </summary>
        public StateValuesProcessor StateValuesProcessor { get; set; }

        /// <summary>
        /// Retrieves and modifies the property values of bound state properties
        /// </summary>
        /// <returns>a collection of bound state property values</returns>
        public PropertyBindingValues GetStateValues()
        {
            if( wizard == null ) return null;
            if( StateType == null ) return null;
            if( State == null ) return null;

            // get the raw values from the state object
            PropertyBindingValues propValues = new PropertyBindingValues(Wizard);

            foreach( ResourceBinder curBinding in this )
            {
                PageBindingResult result = curBinding.GetStateValue();

                PageBindingValue value = result as PageBindingValue;

                propValues.Add(new PropertyBindingValue(curBinding, ( value == null ) ? null : value.Value, result.IsValid));
            }

            // use the default preprocessor if one wasn't defined
            StateValuesProcessor preprocessor = ( StateValuesProcessor == null ) ? new StateValuesProcessor() : StateValuesProcessor;

            return preprocessor.ModifyStateValues(propValues, State, Wizard);
        }

        #endregion

        /// <summary>
        /// Overrides the base implementation to set the PageType and StateType, if one is defined,
        /// properties of objects being added to the collection
        /// </summary>
        /// <param name="index">the index location within the collection where the item is being added</param>
        /// <param name="item">the ResourceBinder object being inserted</param>
        protected override void InsertItem( int index, ResourceBinder item )
        {
            item.PageType = PageType;
            item.Wizard = Wizard;

            base.InsertItem(index, item);
        }

        /// <summary>
        /// Overrides the base implementation to set the PageType and StateType, if one is defined,
        /// properties of objects being set into the collection
        /// </summary>
        /// <param name="index">the index location within the collection where the item is being set</param>
        /// <param name="item">the ResourceBinder object being set</param>
        protected override void SetItem( int index, ResourceBinder item )
        {
            item.PageType = PageType;
            item.Wizard = Wizard;

            base.SetItem(index, item);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Jump for Joy Software
United States United States
Some people like to do crossword puzzles to hone their problem-solving skills. Me, I like to write software for the same reason.

A few years back I passed my 50th anniversary of programming. I believe that means it's officially more than a hobby or pastime. In fact, it may qualify as an addiction Smile | :) .

I mostly work in C# and Windows. But I also play around with Linux (mostly Debian on Raspberry Pis) and Python.

Comments and Discussions