 |
A library for creating Wizards in Swing
Posted by timboudreau on April 17, 2005 at 01:21 PM | Comments (25)
I wrote a prototype replacement for the Wizards API in NetBeans - trying very hard to create something clean and easy to use, especially one which didn't require the user of the API to write any more code than absolutely necessary. It's now a project on java.net and I'd love to get some feedback.
The ideas are pretty simple:
- A wizard does not alter its environment until
finish() is called
- You create a factory for panels. It supplies a list of String IDs for panels, and things call it saying "give me the panel for ID 'foo'".
- The UI you write puts data the user enters into a
Map (listen on a text field, when it changes, call settings.put ("stuffTheUserEntered", theTextField.getText()) and can enable/disable Next/Finish
- The settings map is handled for you - if the user backs up, settings from the current pane are cleanly removed (it's really a map that delegates to a list of per-panel maps)
- The UI is pluggable - it comes with a default one (which still needs polishing and localization) - so if you want a different UI, set of buttons, whatever, you have complete freedom to provide a custom implementation
- Branching and wizards with indeterminate numbers of steps are supported (basically this is conditionally nesting one Wizard inside another, but the convenience class for this makes it painless)
I'm sure it's not perfect yet, and would love to get some feedback. I tried pretty hard to make the javadoc complete and full of examples. You can find the project at wizard.dev.java.net, with the javadoc available as a zip.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Tell me; what is your excuse for not reusing one of the many wizard components out there already ?
Probably because its a non-opensource component. What a waste of time.
Posted by: zander on April 18, 2005 at 05:18 AM
-
That's one of the problems with Swing compared to SWT/JFace, where wizards are standard. In Swing, everybody reinvents their own (I have written a couple of implementations myself, what a waste of time).
No wonder Swing is losing the battle to SWT/JFace.
Posted by: cedric on April 18, 2005 at 09:06 AM
-
Great Work Tim
What about open-sourcing Netbeans Tab Container as a seprate java.net project?
Posted by: swapnonil on April 18, 2005 at 10:33 AM
-
Tab Container: I've thought about it; I'd like to do that; I also don't want to disrupt NetBeans development (and creating a fork isn't really a great idea - at least without a plan to eventually just use a binary from java.net). This project is a fork (well, a copy) of contrib/wizard in NetBeans sources, though I'll probably just delete that version eventually.
Re wizards being standard; why do you think this project is here? :-) So it will be easy to create wizards in Swing. Yes it would be nice if it eventually migrated into Swing itself - I can dream :-)
Re not reusing commercial wizard projects, this is a replacement for a harder to use, already existing API in an open source project. NetBeans is open source; it should not contain commercial code (our users and people who reuse parts of NetBeans would not appreciate this at all, as it would likely impose some licensing burden on them, or at the least they'd have to hire a lawyer to be sure it didn't), licensing something like that for use in an open source project is difficult, and there are a lot of strong feelings about commercial vs. open source out there. Sorry.
Posted by: timboudreau on April 18, 2005 at 02:51 PM
-
I would suggest making the wizard component being hosted at the swingx project (which is the JDNC part dedicated to swing extensions, there are already other out there : collapsable panel, date picker... etc). Something that could be called JXWizard for example.
Posted by: gphilipp on April 19, 2005 at 04:55 AM
-
Probably I should repackage it; it will end up parented to one of the swing related projects. Once I'm absolutely sure what the correct naming convention should be (cvs and renames/moves is pretty ugly, so I'd rather not do it more than once) for wherever it lands, that's what'll happen.
Posted by: timboudreau on April 19, 2005 at 05:18 AM
-
Am I the only one who finds Netbean's governance just bizarre?
Posted by: phlogistic on April 19, 2005 at 11:24 AM
-
Yes.
Posted by: timboudreau on April 19, 2005 at 11:01 PM
-
Tim;
There are more open source wizard components then closed source ones, actually, the question really was not about using commercials ones, I'm sorry if my question was not clear. Since your wizard is still quite basic I would think you did not really spent too much time searching for existing projects and just started working on a new project.
I'll ask the question in another way;
the 5 wizards shown here:
http://wiki.java.net/bin/view/Main/SwingComponents
Could you point out why you can't use them, or work with them?
I would really like to find out if the netbeans maintainer is following the not-invented-here concept.
Posted by: zander on April 20, 2005 at 02:50 PM
-
It had nothing to do with not-invented-here. The inspiration came out of an argument as to whether, to write a wizard API, you needed an intermediate level component that represented a "panel" but was really a factory for the actual GUI panel (to avoid instantiating them all when a wizard is initialized, which causes performance problems), and whether it should be the job of the API user to yank out settings they'd stored if the user hit the back button. I felt that was asking users of the API to write too many classes and do too much work, to do a relatively simple thing. So it started as a weekend thing mainly to demonstrate that you could write a performant, efficient library that saved the user of the API writing a lot of code.
I'll definitely take a look at these other projects, and would be happy to discuss merging things if anyone is interested. There are a lot of other interesting things that can be done to make it easier to design/create panels (make it easy to create good validation code, generate a wizard from an XML file, stuff like that) that this library doesn't touch on.
Another intersting possibility is tools support - have a wizard flow designer. I'm sure the folks working on similar projects have approached the problem set from different angles.
Posted by: timboudreau on April 21, 2005 at 12:37 PM
-
Tim; All of the features and requirements you talked about are already available in the UICWizard , which a friend of mine started years ago (yes, its open source and has grown considerably). Plus various other features like having all control-buttons have the same size all the way to the wizard (in standalone usage) being aware of multi-monitor setups.
I have seen lots of Wizard components being created over the years, most having just a subset of features of the above mentioned. I can understand when newbees don't look further, or even when Gosling likes to create more then to extend. But from you I just expected differently.
Excuse my question on the not-invented-here; with the public image of netbeans being open source, it was the most plausible conclusion. After all; open source is not about sharing your source; but building on top of existing sources.
Posted by: zander on April 22, 2005 at 03:40 AM
-
>open source is not about sharing your source; but
>building on top of existing sources
That's a philosophical position - defensible to a point, but hardly provable. Should I choose not to explore an idea, because somebody has already explored it? The fact that an idea has been explored hardly indicates that there is no more to be discovered. Consider the number of web app frameworks in Java out there - all of which serve slightly different needs and design goals. Should those authors all have witheld their ideas from the world?
UICWizard is interesting; if you'd like to put your friend in touch with me, I'd be happy to discuss collaborating, if that's something that he/she is interested in. Its API is a little heavyweight for my taste - component subclasses are dangerous in and of themselves, and API-wise, I see too many non-final methods in it - it's going to be hard to evolve it cleanly. But if it works and it solves the problem, that's going to be good enough for a lot of users.
Posted by: timboudreau on April 25, 2005 at 07:59 PM
-
No wonder Swing is losing the battle to SWT/JFace.
lol. Not by a long shot. I have never met anyone who uses JFace. Swing is still dominant as it is standard.
The latest Mustang build lets you use any component for the tab titles, this will be very useful (and maybe removes the need to borrow components from Netbeans). NB4.1 is brilliant BTW.
Posted by: jdolphin on April 26, 2005 at 04:27 AM
-
re: UICWizard
any discussion on the topic can be done on either the uic-user mailinglist (see sourceforge pages) or, more personal, on IRC (/msg ThomasZ on freenet) please. Any missing features, design mistakes or simple suggestions are welcome, but might get asked for a more explicit explanation :)
Posted by: zander on April 28, 2005 at 05:43 AM
-
I followed the above link to the list of wizard frameworks out there now. That clearly /cannot/ be an argument for Tim not to work on his wizard project. Those projects are beta at best, look abandoned and are plain mediocre or too much tight into one technology.
Honestly, I am getting tired of reading the 'uh, you are trying something yourself, you must suffer from the not invented here syndrom' argument again and again. Maybe that would be a valid argument when there wouldn't be so much crap out there!
I would be interested in hearing about an elegant, minimalistic wizard framework that isn't tied to a specific technology. I'd like to use that for building a wizard component for Wicket. Currently, Easy Wizard and Wizard Framework are on my short list, of which I think the latter is the most elegant (tied to Swing, but that's less a problem than when it is tied to a webmvc framework). Spring Webflow is not what I am looking for as it introduces more dependencies and is clearly not minimalistic (actually a it is bit bloated if you ask me, but they probably have a lot of flexibility and functionality to justify their API being so extensive).
Posted by: eelco12 on June 13, 2005 at 01:13 PM
-
Interesting. I like the idea of making it generic enough to not be tied to being GUI based vs. web based, but would like to do that in a way that doesn't make the API garbled for either approach - the simplest way to do it would be to replace all occurances of JComponent with Object, but such APIs are weird and non-intuitive (in fact, one of the bugaboos in NetBeans' Wizard API that I wanted consciously to avoid was this method that took an argument of Object for the settings, and you just had to magically know what totally non-obvious type it was to use it). Making this API more generic wouldn't necessarily be that hard; perhaps a layer so GUI authors use a subclass or wrapper that narrows the type to JComponent, and allow other implementations to do what they want. WizardDisplayer is what actually has to manipulate the components that are created, and the implementation of that is pluggable.
Posted by: timboudreau on June 13, 2005 at 01:34 PM
-
I agree you shouldn't sacrifice simplicity and type-safety for genericity in this case. I guess I'll just try to learn from how you and the wizard-framework guy did things, and combine that with my own thoughts and bake something Wicket specific :)
Posted by: eelco12 on June 13, 2005 at 03:39 PM
-
hi,
i have check the wizard api, and tried to build a wizard having 4 panels.
I have just used the WizardPanelProvider class, and
implemented the
JComponent createPanel(WizardController controller, String id, Map settings)
method.
I noticed several issues for me:
contract between controller and created panels :
I think that createPanel has to do too much.
Primarily it shall create the JPanel for the specified id. That's ok.
But the created JPanels also have to initialize its widgets.
Moreover it shall use the settings for writting user input into the setting's map.
In your example code i feel that for simple radio button, and check boxes creating
the swing widgets directly in createPanel is okay, for "real" wizards you will
surley have separate classes holding the JPanels.
I ended up with an implementation decorating the settings map with
a PropertyChangeListener.
This decorated Map I pass on to the JPanel, the radio buttons etc. can set
values in this map.
The setting map act also as initial default value container.
More over I registed a PropertyChangeListener knowing about the
wizard's controller. The PropertyChangeListener can manipulate the
state of the wizard depending on the Map entries.
This way I have separated the wizard JPanel from the wizard.
The only connections between wizard an jpanels are the names, and
types of the map's entries.
I think it is a simple binding-mechanism.
createPanel has some ugly if/else-if code for all the panels.
As the number of possible wizard panels is clear it would be nice to have
some sort of wizardpanel descriptor summarizing:
title, and per wizard panel : id, description, and jpanel.
Speaking in other words making a class from the
parameters of WizardPanelProvider's constructor.
regards bernhard
Posted by: berni on June 13, 2005 at 03:54 PM
-
Interesting ideas.
I figured that if the panel was very complex, one would just do something like
if ("foo".equals (id)) {
return new FooPanel(settings);
}
rather than try to cram it all into an if/then statement. The listenable map idea is interesting as well for making it easier to write panels - I think creating more convenience for writing panels is the next step. If one can assume no-arg, or 1-arg (Map) constructors for panels, we could make it possible to do something like:
new WizardPanelProvider (String[] ids, Class[] panelClasses)
so instantiating the right panel for the right step is free. What do you think?
Posted by: timboudreau on June 13, 2005 at 07:26 PM
-
Allowing
new WizardPanelProvider (String[] ids, Class[] panelClasses)
heads into the right direction.
But you will still have to cope with
controller.setProblem(), and controller.setCanFinish() for each JPanel.
Either inside the JPanel which is ugly, or in the WizardPanelProvider
which leads to the "if/else" if sequences in createPanel.
Let's start from the beginning:
A wizard client (WizCli) wants to use the wizard for setting some values.
In a perfect world it is something like
// some optional predefined values for the wizard panels
MyValue value = new MyValue();
// run the wizard for obtainung newValue
MyWizard myWizard = new MyWizard();
MyValue newValue = myWizard.showWizard(myValue);
// use newValue
A wizard provider (WizProv) is responsible for providing :
(A) the wizard panels, by creating the right JPanel, or JComponent for each wizard panel
(B) triggering the propagation of the settings to the JPanels
(C) listening to setting changes, and deciding if the wizard settings are valid
Note: I skipped here listening to UI widget input, as I think this is the responsibility
of the JPanel creator.
Moreover I'd like to see following constraints met:
Wizard API shall be as less intrusive to JPanel as possible
Describe the number of allowed wizard pages
Describe the expected wizard settings
Allow preset wizard settings
Thus I see following top-level usage of the Wizard API:
// create, and define the settings we are interested in to be set
// by the wizard
WizardSettingsDescriptor wsd = new WizardSettingsDescriptor();
// the wizardId, settings name, settings type, default-value
wsd.add( "vegetarian", "likesMeat", Boolean.class, null );
wsd.add( "mealChoice", "eatsSteak", Boolean.class, null );
// define the wizard panels
// this shall move into FoodPanelProvider
WizardPanelDescriptor wpd = new WizardPanelDescriptor( "Choose Your Dinner" );
// description, panel class
wpd.add( "Food preferences", VegetarianPanel.class );
wpd.add( "Meal Choice", MealChoicePanel.class );
// run the wizard
Map settings = FoodWizard.showWizard( wsd, wpd );
// use the wizard results
if (settings != null) {
// use settings.get( "likesMeat" )
// use settings.get( "eatsSteak" )
} else {
// user has cancelled the wizard
}
VegetarianPanel extends JPanel and implements WizardBinding for passing
the wizard settings to JPanel.
I prefer using interfaces instead of constructor arguments,
but that's a matter of taste.
VegetarianPanel reads the settings once and adopts its UI widgets accordingly.
It listens to the UI widgets and sets entries in wizard settings accordingly.
A single or per JPanel PropertyChangeListener listens to wizard settings changes,
and checks the mapping settings, depending on the mapping settings it
manipulates the controller by invoking controller.setProblem, controller.setCanFinish.
The trick is that the PropertyChangeListener listens already when the entries
from WizardSettingsDescriptor are merged into wizard settings.
This is done when the JPanel is created.
In this case the wizard controller is initialized properly, and the UI widgets
are initialized properly.
What do you think?
Posted by: berni on June 14, 2005 at 11:56 AM
-
I'd like to respond in detail, but this blog is getting pretty long - how about we move the conversation to the dev mailing list? (click the link on the left on the wizard homepage).
In essence, in putting together the initial version of the library, the top priority was having a very clean API for handling navigation, but very little concern for how someone would write the panels (so currently, you pretty much do what you want); clearly that's the next step.
Let me know here when you've signed up for that list (or let me know if you absolutely don't want to for some reason - no pressure :-).
Posted by: timboudreau on June 14, 2005 at 12:15 PM
-
Honestly, I am getting tired of reading the 'uh, you are trying something yourself, you must suffer from the not invented here syndrom' argument again and again. Maybe that would be a valid argument when there wouldn't be so much crap out there!
I agree. Pretty sad, Thomas, that you have to astroturf other people's projects to promote your own.
Posted by: reckstei on July 18, 2005 at 06:59 AM
-
Hi Tim.
I have recently started using your wizard package. The one fly in the soup for me that I have stumbled upon is the fact that WizardDisplayer runs as a modal component, i.e. it blocks everything else. I'd like to be able to run multiple instances, i.e. in non-modal form. Since there is no source-code I cannot easily hack my own. So could you povide a handle/constructor that would run the display simply in a JFrame, or as non-modal component. Alternatively either please provide the source code (or an example) of WizardDisplayer, so that I may make a non-modal implementation of it.
regards, JanC
Posted by: cernohj on August 18, 2005 at 08:08 AM
-
No source code? It's all available from CVS right here.
cvs -d:pserver:USERNAME@cvs.dev.java.net:/cvs login
cvs -d:pserver:USERNAME@cvs.dev.java.net:/cvs co wizard
Yes, it's looking like a way to get
a component representing a wizard may be needed. Thanks for the feedback!
Posted by: timboudreau on August 18, 2005 at 08:30 AM
-
FYI, for those following this topic, I've done some major revs to the Wizard project in response to user feedback. In particular, there is a new convenience panel class. Just add your components to it and it will automatically listen to them; if the Name property is set on the components, when the user changes the value, it will automatically write the new value into the settings map. If you want to do validation, override one method.
Then just call WizardPage.createWizard ( Panel1.class, Panel2.class, ...) and use WizardDisplayer to show it.
Posted by: timboudreau on August 22, 2005 at 03:16 AM
|