Adding an extra (optional) page to the C# Wizard

roger's picture

This seems to be causing a few problems, so I'll quickly walk through it. I'll try to throw some screenshots in later.

In the TestWizard project, choose "Add New Item". In the dialog, choose "Inherited User Control" (it's under Windows Forms). Call it OptionalPage.cs. The Inheritance Picker will appear. Choose "InternalWizardPage".

In the forms designer, click on the banner and change the Title and Subtitle properties to "Optional Page" and "This page is optional".

In the constructor for TestWizardSheet, add a line to add the OptionalPage:

            this.Pages.Add(new WelcomePage());
            this.Pages.Add(new MiddlePage());
            this.Pages.Add(new OptionalPage());
            this.Pages.Add(new CompletePage());

If you run the project now, you'll see that the new page has been inserted in the correct place.

But we want to make it optional, so throw a check box onto MiddlePage. Add an event handler for WizardNext. Make it look like this:

        private void MiddlePage_WizardNext(object sender, WizardPageEventArgs e)
        {
            if (checkBox1.Checked)
                e.NewPage = "OptionalPage";
            else
                e.NewPage = "CompletePage";
        }

Now, when you run through the wizard, the optional page doesn't appear unless the check box is checked.

But wait. It does appear if you press "Back" on the final page. We'll have to be a bit cleverer. Create a class like this:

    public class TestWizardContext
    {
        private bool _showOptionalPage;

        public bool ShowOptionalPage
        {
            get { return _showOptionalPage; }
            set { _showOptionalPage = value; }
        }
    }

Create an instance in TestWizardSheet's constructor and pass it to each of the pages that care:

            TestWizardContext context = new TestWizardContext();

			this.Pages.Add(new WelcomePage());
			this.Pages.Add(new MiddlePage(context));
            this.Pages.Add(new OptionalPage());
			this.Pages.Add(new CompletePage(context));

You'll need to add a _context field to MiddlePage and CompletePage and initialize it in the constructor. Then, create a handler for the checkbox:

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            _context.ShowOptionalPage = checkBox1.Checked;
        }

Then, add a handler for WizardBack to CompletePage:

        private void CompletePage_WizardBack(object sender, WizardPageEventArgs e)
        {
            if (_context.ShowOptionalPage)
                e.NewPage = "OptionalPage";
            else
                e.NewPage = "MiddlePage";
        }

Job done.

Comments

Thanks a LOT for your

Thanks a LOT for your explanation, however I'm still a small step from getting it to work. I have tried to add an evenhandler for the checkBox, but I'm having some trouble with it. The compilers gives an error saying: Error 1 Cannot implicitly convert type 'Wizard.UI.WizardPageEventHandler' to 'System.EventHandler'

this.checkBox1.CheckedChanged += new Wizard.UI.WizardPageEventHandler(MiddlePage1_WizardNext);

Francis

You've wired the wrong handler to the event

You need separate handlers for CheckedChanged and for WizardNext. You've attempted to wire the WizardNext handler to the CheckedChanged event.

Thanks again! I finally got

Thanks again! I finally got it working perfectly, and I am very very grateful for you assistance!

How to pass the data between

How to pass the data between different wizard pages?

Re: How to pass the data between

Use a context object, and pass it to the constructor of each page that needs to access it. This can be as simple as passing a Dictionary<string, object> object to each page.

Thanks alot. I got it!

Thanks alot. I got it!

Flow Style

Eek! Well first let me say this is an excelent wizzard and I thank you. My eek is in reference to the solution to the "optional" page. I am trying to make a wizard that is based off of a flow diagram. It is not overly complicated but somewhat. It does resemble a tree though. e.g. If optionA goto pageB if optionB goto pageC if optionC goto pageD then ofcourse pageB pageC and pageD do simliar things. Also there will be pages inbetween in some cases that display data (to help the user pick optionA, optionB, or optionC) So this "optional" page idea does not seem to be the best solution. Since all of the pages past the first would in actuallity be optional. If you have any ideas please let me know. I am thinking if the InternalWizardPage is broken down differently and combined with a class to keep track of instantiations then the next and back buttons will all link up properly. Anyways I will keep playing with it and post if I come up with something. Thank you again.

Re: Flow Style

One potential enhancement is simply to have the navigation stored on a stack. The Back button would then simply pop pages off the stack. This would make the later pages much simpler, because they wouldn't need to know where to return to.

To simplify it further than that, one might decide to discard the simple list of pages, and to replace it with a collection of lists. That is: if the user chooses option B on the first page, then all of the next pages come from this other list. I'm not sure how well that'll scale.

To be honest, if I ever get time, I'll be revisiting this project to update the styling to the one that MS are using these days, rather than the (dated-looking, but still cool) Wizard97 style. At the same time, I might see if I can do some separation-of-concerns stuff -- i.e. look at an MVC or MVP or MV-VM pattern for this stuff.

Or I could just redo the lot in WPF...

The stack idea is exactly

The stack idea is exactly what I was thinking for the back button. However for next I decided to create a different InternalWizardPage. I created one that inherits from it (just to keep it simple) set up the buttons with in it (cause that annoyed me to have to do every time I made a new page).

The true key was creating 2 Lists

        List _rbList = new List(); //Used to link the radio button to the next page
        List _nextPages = new List(); //Actual next page

as well as a method to add radio buttons dynamically

public void AddRadioButton(RadioButton rb, string nextPage)
        {
            AdjustLocation();
            rb.Location = _location;
            rb.TabIndex = _tabIndex++;
            rb.CheckedChanged += new EventHandler(rb_CheckedChanged);
            this.Controls.Add(rb);
            _rbList.Add(rb); //Add to the list for easy reference
            _nextPages.Add(nextPage);
            Refresh();
        }

        void rb_CheckedChanged(object sender, EventArgs e)
        {
            if (((RadioButton)sender).Checked)
                _selectedRB = (RadioButton)sender;
        }

        private void AdjustLocation()
        {
            _location = new Point(_location.X, _location.Y + _locOffset.Y);
            if (_location.Y > _bounds.Y)
                throw new ArgumentException("Added too many RadioButtons and the control will now draw out of bounds");
        }

So then the instantiator of this component just needs to send in a RadioButton with its linked page. I created a MakeRadioButton method to set up the basics then I just set the name and text manually before sending it to the page instatiation.

private RadioButton MakeRadioButton()
        {
            RadioButton rb = new RadioButton();
            rb.AutoSize = true;
            rb.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            rb.Size = new System.Drawing.Size(335, 24);
            rb.TabStop = true;
            rb.UseVisualStyleBackColor = true;
            return rb;
        }

Thanks again for your help :)

optional pages

Hey, Just wanted to let you know what a great wizard you have created! I've been using it as a backbone for a project at my work. I have a quick question, and maybe I need to send you the code because I have made a number of modifications. The optional page tip is not working for me. I have changed the sheets initialization to this:
this.Pages.Add(new WelcomePage());
WritableSheet[] pageList = new WritableSheet[9];
pageList[0] = new MiddlePage();
pageList[1] = new MiddlePage1();
...
foreach (object page in pageList)
this.Pages.Add(page);
this.Pages.Add(new CompletePage(pageList));

All my pages are added correctly, I then created a checkbox on my 5th page and added the code you had included. For some reason the code is not working and whether the checkbox is or is not checked, the page shows up. If you have time, please help me out. Thanks again for such a great start at a wizard! -Laura

e.NextPage

e.NextPage takes the Name of the page. If you have two pages with the same name (which can happen -- the designer's not that smart), then you might run into trouble.

Without seeing more of your code, I can't really diagnose the problem. Sorry.

You could, I guess, change the code to take a Type, and then it could look for the first page of that type.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.