Click here to Skip to main content
15,885,941 members
Articles / Desktop Programming / Windows Forms

Composite UI Application Block (CAB) Wizard

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
13 Mar 2009CPOL4 min read 30.6K   279   15   2
Shows a very simple way to create a wizard in CAB

Introduction

In this article, I show a fairly simple implementation of a Wizard in CAB. I did something similar at the company I work for and decided that it might be useful to someone else out there with similar needs.

I'm assuming that you know the basics of CAB, this is a really elementary implementation of a Wizard and I won't be delving into massively complicated CAB capabilities. I'm also certain that this wizard could be done better using some of the more advanced features of CAB, but it suffices, and can be useful in a production environment.

Background 

One of the things I tried hard to do with this wizard was to make it easy to implement, whilst at the same time staying true to the basic tenets of CAB. I didn't want to have to write an entire guidance package just so the wizard could be used. I'll be discussing why I did things the way I did as I go through the code.  

Code 

I'm using VS 2005, the CAB framework and the Patterns and Practices Guidance Packages.

I started with a basic CAB shell, happily generated for me using the guidance packages. Good times.
I added 3 CAB Modules, and a Class Library project. The first two modules (cunningly named ModuleA and ModuleB) contain a single view each to illustrate that views from different modules can be shown in the wizard. The third module contains the view which we will be using to host the views in the wizard. 

The class library simply contains two interfaces, one which goes on the UserControl of any view you wish to go into the wizard, and another which goes onto the presenter of the same. These interfaces allow us to treat any view, no matter which module it is in, the same way, allowing us to keep the mantra of the CAB project (No Module will reference any other module).

SolutionExplorer.JPG

The interfaces:

C#
namespace WizardInterface
{
    public interface IWizardView
    {
        IWizardPresenter Presenter { get; }
        int Priority { get; set; }
    }
} 
C#
 public interface IWizardPresenter
{
    bool Startup();
    bool Shutdown();
} 

The implementation of the interfaces in WizardView1:

C#
namespace CABWizard.ModuleA
{
    public class WizardView1Presenter : Presenter<IWizardView1>, IWizardPresenter
    {
        /// <summary>
        /// This method is a placeholder that will be called by 
        /// the view when it's been loaded <see cref="System.Winforms.Control.OnLoad"/>
        /// </summary>
        public override void OnViewReady()
        {
            base.OnViewReady();
        }

        /// <summary>
        /// Close the view
        /// </summary>
        public void OnCloseView()
        {
            base.CloseView();
        }

        #region IWizardPresenter Members
        public bool Startup()
        {
            return true;
        }

        public bool Shutdown()
        {
            return true;
        }
        #endregion
    }
}
C#
namespace CABWizard.ModuleA
{
    [SmartPart]
    public partial class WizardView1 : UserControl, IWizardView1, IWizardView
    {
        public WizardView1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Sets the presenter. The dependency injection system will automatically
        /// create a new presenter for you.
        /// </summary>
        [CreateNew]
        public WizardView1Presenter Presenter
        {
            set
            {
                _presenter = value;
                _presenter.View = this;
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            _presenter.OnViewReady();
        }

        #region IWizardView Members

        IWizardPresenter IWizardView.Presenter
        {
            get { return _presenter; }
        }

        private int priorityField;
        public int Priority
        {
            get { return priorityField; }
            set { priorityField = value; }
        }

        #endregion
    }
} 

You will note that I have to expose the Presenter as a public property on the view. I really didn't want to have to do this, instead I wanted to communicate with just the presenters, and allow the Presenters to control the Views. Unfortunately, the dependency injection on the Views which instantiates the presenter is only performed with the View added to a WorkItem. Unfortunately, if I'm passing around Presenters instead of Views, I patently need them beforehand. Creating a Presenter by hand is also messy, because you have to manually set up the WorkItem and other base properties on the presenter. It's just a giant pain.

In terms of getting the list of views for display we throw events around. One event, to which all modules must subscribe, asking for a list of views, and more events to pass the instantiated views back to the WizardModule.

The RequestForView event in the WizardModule, and the subscription in ModuleA:

C#
[EventPublication(EventTopicNames.RequestForViews, PublicationScope.Global)]
public event EventHandler<EventArgs<List<string>>> RequestForViews;
protected virtual void OnRequestForViews(EventArgs<List<string>> eventArgs)
{
    if (RequestForViews != null)
    {
        RequestForViews(this, eventArgs);
    }
} 
C#
[EventSubscription(EventTopicNames.RequestForViews, ThreadOption.UserInterface)]
public void OnRequestForViews(object sender, EventArgs<List<string>> eventArgs)
{
    List<IWizardView> views = new List<IWizardView>();
    foreach (string s in eventArgs.Data)
    {
        switch (s)
        {
            case "WizardView1":
                {
                    WizardView1 view = new WizardView1();
                    view.Priority = eventArgs.Data.IndexOf(s) + 1;
                    views.Add(view);
                    break;
                }
            case "WizardView3":
                {
                    WizardView1 view = new WizardView1();
                    view.Priority = eventArgs.Data.IndexOf(s) + 1;
                    views.Add(view);
                    break;
                }
        }
    }
     OnReturnRequestedViews(views);
} 

Now the event used by ModuleA to pass the list of views back to the WizardModule:

C#
[EventPublication(EventTopicNames.ReturnRequestedViews, PublicationScope.Global)]
public event EventHandler<EventArgs<List<IWizardView>>> ReturnRequestedViews;
protected virtual void OnReturnRequestedViews(List<IWizardView> eventArgs)
{
    if (ReturnRequestedViews != null)
    {
        ReturnRequestedViews(this, new EventArgs<List<IWizardView>>(eventArgs));
    }
} 
C#
[EventSubscription(EventTopicNames.ReturnRequestedViews, ThreadOption.UserInterface)]
public void OnReturnRequestedViews(object sender, EventArgs<List<IWizardView>> eventArgs)
{
    viewList.AddRange(eventArgs.Data);
     //All the views that we asked for, have arrived. 
     //If they never arrive (i.e., a module didn't load) at least nothing *breaks*.
    if (viewList.Count == viewNames.Count)
    {
        viewList.Sort(delegate(IWizardView i, IWizardView j)
                    {
                        return i.Priority.CompareTo(j.Priority);
                    });
        viewList.Add((IWizardView)new EndOfWizard());
         ShowWizardHost();
    }
}
 private void ShowWizardHost()
{
    WizardHost wizardHost = new WizardHost(viewList);
     WorkItem.SmartParts.Add(wizardHost, ViewNames.WizardHost);
    WorkItem.Workspaces[WorkspaceNames.RightWorkspace].Show(wizardHost);
}

Note that here is where I've taken a shortcut, and left the rest to you. The list of view names (or whatever other method you decide you want to use to get the views) is hardcoded, you could use a service, a wizard builder or whatever other mechanism you want here.

We create a new Workspace on the WizardHost View, and then add the Workspace to the WorkItem. We then show the first wizard view in the WizardHost's DeckWorkspace. There is some simple logic for the next and previous buttons, and bob is your father's brother.  

The most important thing on the presenter is your Startup method. If this returns false, the Wizard Host will skip over this view and move on. You can still execute code here before returning the boolean. So if you need an 'invisible' view as a save point, you can have it. The startup method is also used to set up the data in the view, call a database, whatever the view needs.

You will also note that the WizardView1 comes up twice. This is just to illustrate that you can have more than one of the same type of view in the wizard, useful if your view performs more than one role. To check the behaviour of the wizard, set the return true; value on wizardview1 to return false;. Watch as the wizard carries on without any problems, skipping both instances of WizardView1.

Points of Interest

This is a *very* basic wizard. There are a lot more features you can add such as:

  • Adding properties to adjust the behaviour of the Wizard to ensure you cannot continue past a View until that View is satisfied that all criteria are met on the View(Validations for example). 
  • With a fair amount of cunning you could make a 'dynamic' Wizard which allows the list of Views to be changed 'on the fly'. 
  • You could add a progress bar to the Wizard host showing which view the user is currently seeing, and how many steps are left.
  • You could certainly make it a lot prettier :)

History

  • 13th March, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Chris is a full time software developer in London, UK.
He has a BSc in computer science and is busy taking courses in the MCTS stream.

Comments and Discussions

 
GeneralUsing a third Party Control Pin
CreF13-Mar-09 6:20
professionalCreF13-Mar-09 6:20 
Hi Chris,
first of all nice job.
Then...Consider that I'm a newbe at CAB.
I'm trying to use a third party component that the Company I work for bought. Is the Component Factory Krypton Navigator.
I added it to your WizardView1 placing the label inside it.
But when I try to build the solution it prompts for an error:

Error 1 The type 'ComponentFactory.Krypton.Toolkit.VisualSimple' is defined in an assembly that is not referenced. You must add a reference to assembly 'ComponentFactory.Krypton.Toolkit, Version=3.0.0.0, Culture=neutral, PublicKeyToken=a87e673e9ecb6e8e'. C:\VS2005Projects\C#\CAB\CABWizard\ModuleA\ModuleA\Views\WizardView1\WizardView1.Designer.cs 287 67 ModuleA

I added the reference to that Navigator in the references to all the projects in your solution, but nothing changed... Same error and it doesn't buil anything.
If I remove that component, everything works fine...
Do you have, perhaps, an idea on which is the problem and how to solve it?
Thanks in advance
Francesco

the CreF

GeneralRe: Using a third Party Control Pin
anarchistic13-Mar-09 19:24
anarchistic13-Mar-09 19:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.