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'
Francis
You've wired the wrong handler to the event
Thanks again! I finally got
How to pass the data between
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!
Flow Style
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 pageas 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
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.