Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looking for a great Wizard example implemented with WinForms and/or an advice regrding design [closed]

Disclaimer: In this question tab, page, an dialog actually mean the same thing, sorry. My excuse: I am not sure what the final product should look like - a bunch of separate windows or all in one.

I am looking to improve an existing, hard-to-maintain Wizard baked with WinForms. I need to try to keep the look and feel about the same, but I need to clean up the internal logic. There are 5 dialogs in total, all of which get displayed one after another (after the Next button is clicked of course) inside one giant method. The way to jump back and forth is with ... 5 or 6 labels and GOTOs!

Now, this Wizard is linear, not a tree. From any one dialog/page you should be able to go to at most two others. Somehow doubly0linked list comes to mind. Right now there are 5 * 4 = 20 potential state transitions, while only 2*1 + 3*2 = 8 of them are valid. I do not have to use gotos. They are usually evil, and in this case they are - it is hard to maintain this already... and I am thinking of adding another, 6th page. The reason why gotos are in there are most likely because A) time pressure when v. 1.0 was being made, B) It was 5 years ago, so the best examples/tutorials on Wizards available at the time may not have been great.

Now, most pages of the Wizard ask for user's input. The subsequent pages are rendered depending on what the user have entered. If the user is say on page 3 and decided to ht a back button all the way to 1, and has not changed anything, and hits Next twice, then the state should not change. However, changing things on page x will generally invalidate stuff on pages x + 1 and further. There are exceptions to this, however, as some or all settings on a page x might depend on page x-1, x-2, etc., but pages x+1, x+2, etc do not depend on that x for some x.

I hope things are clear so far. We try to help the user by defaulting some stuff for them. The way things are stored is not great either. The dialog has read/write properties, from/to which stuff is copied to/from the actual controls. Then, in the main method, there is a "super-storage" which holds storages for each page. So, when the user is done with page x and hits next, stuff is first being copied from controls into storage that is local to the class, and then that stuff gets saved into the appropriate member of the super-storage.

Arrays (of dialogs/storages) and indices are not being used. There is separate yet similar "create&populate" logic for every goto destination (label). The dialogs objects get thrown away when the page is no longer displayed (they are not disposed, but every time they are to be shown, they get re-created and re-populated anew. I do not believe this is necessary, as only a single handle is needed, and after it has been shown, and closed, I believe it can be shown again in the same state, without having to re-populate controls. If wasting memory was the only issue, I would probably let things slide, but the thing is not very maintainable, so I might as well fix it all up.

I am thinking:

  1. Store dialogs in a collection, such as an array, but preferably D.L.L. because I can only move forward 1 or backward 1, or only one of the two options I listed (for the first and last dialog).
  2. Actually have my tabs/pages all extend a common abstract class (because "next", "back", "exit" buttons and their behavior are common to all).
  3. Each tab/page/dialog (same thing for the purpose of this question, sorry for confusion) will have read-only properties visible to the "conductor" class. These properties will derive from the values in controls (the true source of info), sometimes the properties will massage these values a bit. It will be the responsibility of the "conductor" to grab those and put them into storage. When the conductor wishes to populate the dialog with a single method (let's call it "seed"). I have a bit of difficulty here, since the parameters for each seed method will be different. I want to be able to both take advantage of strong typing, as well as keeping things generic. I suspect that something ought to give. I could pass in a dictionary to each seed method, but that feels too Pythonic, like duck typing. I would not know until the run time if I screwed up. Also, the packing and unpacking of the dictionary better always match, for any given page. This is where you would come in.
  4. The global storage can be one giant dictionary. I can be disciplined enough to keep all keys different, or prefix their names with "p1_" through "p5_" depending on a page, just to be sure. I am sure that other schemes exist as well. Having on giant dictionary can be convenient at the very end - there the order in which user input was assembled will not matter, as long as it is done correctly. I can also have a state machine ... sort of. This is where I am also getting lost in the design. If I keep things in a dictionary, I would have to perform a lot of conditional logic, such as: if I am on page 2, and I make a change, then I usually (there may be exceptions) need to make old defaults if any for pages 3,4,5 invalid. Depending on how ugly it gets, it might not be much better than the current goto-based design. I think I can do better, however, as I can implement my state or state transition-specific logic with a bunch of delegates, handles to which are stored in two dictionaries (one for next, one for back), where current state is the key.

As you can see, there are a few challenges. I am hopeful, however, because thinking through a good Wizard design is a wheel that surely was [re-]invented before. Perhaps you can recommend an open source C# / mono app which come with a linear, yet non-trivial Wizard, so that I can take a peek at the implementation. Heck, maybe even Java/Swing will probably suit me as long as the Wizard is similar in nature. WPF would be yet another challenge for me, I do not want to have 2 problems instead of 1.

Let me know what you can think of. Should I just keep the gotos but clean up other parts as much as I can? Feel free to ask questions. Thanks,

-HG

like image 961
Hamish Grubijan Avatar asked Aug 12 '10 03:08

Hamish Grubijan


1 Answers

I have some WInForms wizard code this is the general architecture:

On main wizard have StepList As List(Of baseWizardStep) .. list of all possible steps which is created when the wizard main form is.

Also Next Back and Cancel buttons.

As with Alex above, have a base class (baseWizard) main form with a panel that hosts a user control for the current step. Each wizard step itself is a user control (Inherits from baseWizardStep that inherits from UserControl) baseWizardStep has some built in events (ArriveAtStep, ValiateStep, LeaveStep etc..) Also in an InitliaseStep sub that takes main wizard as a parameter and stores a ref to main wizard form in property. All the steps access the main wizard to store their data in properties/objects/datasets etc.. (Storing of data is usually done on the LeaveStep event) The steps load data into their controls on the ArriveAtStep event.

I sometimes need to share wizard steps amongst many wizards, in which case the mainWizard implements an interface and the wizard steps cast their main wizard property to the interface to access/store data.

I have 2 events top control flow through the wizard.

If you don't handle any of these 2 events, then the wizard goes through from 1st in StepList to the last in order one step at a time.

Event 1: If you handle ShoudlStepOccur this event allows developer to decide should a step occur in the list of steps (so you can skip steps) This seems to cope with most wizards in an easy to undserstand metaphor. Event gives you the step and the diurection (Forward, back)

Event 2: (advanced control) THen there is the other event on the main form NavigateToStep, event gives you the step it was intending to go to, but you can change it to navigate to a completely differeent step. We use this to go round in a loop many times (Eg. In the Enrol on a course wizard, we go throurgh some steps of the wizard many times once for each course)

I also have a grid that lists all steps with the current step highlighted and that the user can click on to jump thourgh the wizard. I use the StepShouldOccur event to know which steps to disdplay in the step list at any time.

One gatchas: -When close wizard, you must dispose the steps that are no currenlty hosted in the panel and just lying around in the StepList becuase they will not releae their windows handles otherwise.

Not sure how much sense that's making so I'm going to leave it there.

Maybe one of these days I'll put this wizard code up on code project or something, I'm quite happy with how it works for us.

like image 56
MikeG Avatar answered Nov 16 '22 17:11

MikeG