Adding an extra (optional) page to the C# Wizard
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.
I was making a dumb mistake
hmm???
It's a field
You do something like this:
class Foo { private readonly Context _context; public Foo(Context context) { _context = context; } // ... }Just wanted to drop in and
How to reuse existing forms in a wizard?
For instance, my wizard has 3 pages:
Set up Email Accounts
Set up Email Templates
Set up Search Criteria
In my actual application, I have 3 different forms, all programmed to gather this information (and logic too, to understand when text is input, do this, or if the form is filled out incorrectly throw an error, etc).
Using your wizard pages code, it seems that I cannot simply do something like:
(create a new wizard page for EmailAccounts) then in the 'SetActive' function, I can't simply "bring in" (Form emailForm = new EmailAccounts(), then emailForm.Load() or something) the existing code and functionality - I have to redo ALL of the code to get the same behavior I already have in my program.
Does my question make sense? Is there actually a way to do what I would like, and save me the hassle of duplicating the already existing work/functionality somehow? Feel free to e-mail me about this if you could, I'm not sure if I will get a notification when (and if) you reply.
Thanks!
Re: How to reuse existing forms in a wizard?
I'd do this by creating your 3 forms as separate user controls. Then, have your other dialogs be essentially empty, and then put the relevant user control on each one.
You'd then do something similar with the wizard pages -- create a placeholder that has your user control on it.
In this way, you can share the design and logic between the two types of dialog.
Did you see the link to the paged options dialog in part 1 of this series on the wizard? That might be a good UI for you.