Wizards are easy!
Have you ever wondered how to write wizards like Delphi's database wizard? It looks as if each step of the process is a separate form displayed in the same position as the previous one, with buttons at the bottom to move to the next step or the previous step in the process. It may well be implemented this way, but there's a much easier way using a page control and a little known property of tabSheets.
If you're not really familiar with wizards, use Delphi's database form wizard to generate a simple database form and you'll see what I'm talking about. A wizard guides the user sequentially through several steps of some process. Each step of the process displays what appears to be a separate form. Each of these forms contains components reflecting the current step, and the bottom of each form has a common set of buttons, usually labeled Next, Previous, Finish and Cancel.
As I said, an easier way to implement this in Delphi is to use one form with a panel at the bottom displaying the buttons, and the remaining area being occupied by a pageControl. Each step in the process is then a separate tabSheet, and all the buttons need to do is move between different tabSheets. Now, and this is the trick, if that's all you do the form will just look like a PageControl with multiple tabSheets. The key is to make the little tabs of each tabSheet invisible. The TTabSheet control has a property called tabsVisible - set each one to false and the user will never realize they are actually using a page control.
One irritating consequence of setting the tabsVisible property false is then you can't see the individual tabs at design time, when you are laying them out with components. What I do is leave tabsVisible set to true ar design time, then in my form's onCreate even loop through each tabSheet and set its tabsVisible property false. I also use this event to set the pageControl to initially show the first tabSheet. If you don't do this, Delphi will start the pageControl showing the last tabSheet you worked on at design time - another minor irritation. Here's the code from the onCreate event:
// Wizard form's onCreate event
For i := 0 to PageControl1.PageCount - 1 Do
pageControl1.Pages[I].tabsVisible := false;
PageControl1.activePageIndex := 0;
Not how it's easier to use the pageControl's activePageIndex property rather than the activePage property.
The buttons to move between pages simply increment or decrement the activePageIndex property, and call a routine to enable or disable the buttons appropriately (we'll see this code in a moment):
// Next button
PageControl1.activePageIndex := pageControl1.activePageIndex + 1;
// Previous button
PageControl1.activePageIndex := pageControl1.activePageIndex - 1;
Finally, you need to enable / disable the buttons according to which step of the process the user is on. The previous button should be disabled when the user is on the first step, and the next button disabled when the user is on the last step of the process. The finish button should only be enabled when the user is on the last step of the process. This context sensitive enabling / disabling of controls is such a common operation that all forms I write have a centralized setState method, which as the name implies sets the enabled / visible properties of controls. I call this one routine every time I need to set the state - in this case from the form's onCreate routine and each time I change the active page.
BtnPrev.Enabled := (pageControl1.ActivePageIndex <> 0);
BtnNext.Enabled := (pageControl1.ActivePageIndex <> pageControl1.pageCount - 1);
BtnFinish.Enabled := (pageControl1.ActivePageIndex = pageControl1.pageCount - 1);
A final note - this is an ideal example of a form you should place in the repository. The basic form, with the pageControl and the buttons, and the code for the buttons, is not tied to any particular wizard - put this form in the repository then inherit from this form to create actual wizards - all you need to do in the new form is add tabSheets and lay them out - the rest of the logic is already written.
Until next month!