 |
JFrame.add() contentPane Pain: The Complete Story
Posted by hansmuller on November 16, 2005 at 11:35 AM | Comments (10)
It's my fault. The fact that adding a component to a JFrame required
one to explicitly add it to the JFrame's "contentPane" is my fault.
Early on in Swing's evolution we added a runtime exception that warned
developers not to write JFrame.add(myComponent) and it has been
raising hackles ever since. Graham Hamilton covered my transgression
in his
My Favorite (Dead) Java Boilerplate
blog and I thought I'd explain the rationale behind it's birth
and eventual demise.
It turns out that I did not create this trip-wire to incite violence
or to "educate developers about the choices" within the JFrame container.
JFrame's automatically created rootPane,
layeredPane, and contentPane substructure was designed to enable popup
effects that appear on top of the main GUI. The original motivation
for JFrame's substructure was we to support lightweight menus and
tooltips and even dialogs that appeared within a top level window.
It's also possible to use the substructure to produce novel GUI
effects,
like translucent full-window progress monitors. So why did
JFrame.add() generate an exceptional slap in the face for the
developer who's not schooled in all of this?
The 1.0 and 1.1 releases of Swing were delivered on the original Java
1.x platform. Our audience was AWT developers who typically wrote
small apps by subclassing java.awt.Frame and overriding paint() or
setting its layout manager and adding children. When we decided to
create JFrame's substructure there was a debate about the wisdom of
automatically mapping JFrame.add() to JFrame.getContentPane().add().
The reason I rejected that approach is that this "convenience" is a
shallow illusion. To complete the illusion one would have to redirect
get/setLayout(), and addComponentListener(), and getComponent() and
getChildren() and so on. In addition to making it tough to actually
get inside the JFrame itself, the complete illusion would be
asymmetrical since the source of events or a layout manager's container
wouldn't match what a developer would expect. So in the interest of
consistency, not education, we did not automatically redirect
JFrame.add() to the content pane.
Time has passed and the number of AWT developers who's expectations
might have been violated by making JFrame.add() convenient has become
pretty small. It's also true that the merits of providing a simple
trouble-free out of the box experience, even if it depends on an
imperfect illusion, are increasingly important. So, in Tiger,
JFrame's add and setLayout (and addLayout) methods have changed to "do
what you [probably] mean". The other JFrame methods, like getComponent(),
do not redirect, so if you use them, be careful. And if you don't use them,
well, ignorance is bliss.
Now that we've got that out of the way, anyone have a nomination for
a new Swing boilerplate hall of shame candidate? As far as I know,
the rest of the API is perfect.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
How about providing a constructor of BoxLayout that doesn't need the container reference? However, that was minor. The real issue for (not only beginner) Swing programmers is to decide when to use SwingUtilities.invokeLater. It looks horrible in code (especially since modern IDEs make it so easy to format the code, and you very quickly reach ungodly starting column indexes) when you use it, and it bites in the behind when you don't. Can't Swing just "guess" (as you put it yourself) that when a UI property is changed from within EHT, then it must be "put on hold" in the event queue? It may not be an easy task, but can make our jobs a lot easier.
Regards
Kirill
Posted by: kirillcool on November 16, 2005 at 01:45 PM
-
Hans, will there be any issues with backwards compatability for Swing apps developed on earlier releases of Java SE? I know that typically the team is cognisant of these types of things.
Todd
Posted by: viegs on November 16, 2005 at 02:37 PM
-
[Kirill] Changing UI properties from the event dispatching thread is usually
OK however you're right that writing async code to handle tasks that
should run on a worker thread can be messy. Providing some intrinsic
support in an Action framework for running the action on a work
queue thread might be an improvement. Making sure that such tasks
can be interrupted is a bit tricky and identifying what parts
of the GUI should be disabled (or otherwise visually reflect that
something is going on in the background) would also serve to complicate
matters. And yet I do think we could find a way to make a net
improvement over writing invokeLater() code by hand.
Posted by: hansmuller on November 16, 2005 at 02:44 PM
-
[viegs] I don't think we've had any complaints about bugs due to
backwards incompatibility per the JFrame.add() etc changes, since the
old version of the methods just threw a runtime exception. I doubt
any working programs used them. Generally speaking we're very
cautious about making changes that might break compatibility
(occasionally even bug fixes even suffer this fate). You might say
that making the semantics of JFrame.add() incompatible with
Frame.add() taught us a lesson.
Posted by: hansmuller on November 16, 2005 at 02:47 PM
-
Why not @deprecate the add() and setLayout() methods?
At least then no one would be ignorant because they would get warnings at compile time.
Or is that too much of a misuse of @deprecate because it implies that the methods will be removed at a later date?
Posted by: jonesdean on November 16, 2005 at 02:52 PM
-
[jonesdean] I agree that deprecating those methods would have sent
roughly the same (disagreeable) signal that the exceptions did.
Still, we never planned to remove the JFrame methods and most uses of
@deprecate are, well, deprecated. The goal of the change was to
smooth over a bump that tripped developers who'd either cut their
teeth on AWT or felt that reading the complete JFrame
root/content/layerPane substructure documentation was a waste of time.
God intended JFrame.add() to work a particular way after all :-). I
should point out that the Java tutorial does a nice job explaining
JFrame's mysteries, see:
How to Make Frames (Main Windows)
.
Posted by: hansmuller on November 16, 2005 at 03:12 PM
-
I'd like to nominate menu construction for the Boilerplate Hall of Shame.
Get the menu name from the resources.
Parse out the mnemonic indicator. Set mnemonic and mnemonic index.
Remove mnemonic indicator and set menu name.
Get the accelerator from the resources.
Deal with menu shortcut key mask to handle Mac accelerators
Translate into KeyStroke
Set accelerator
Set icon
Of course, everyone and their brother has written a helper class for this purpose. And everyone's helper class is different.But it is never too late to do the right thing. I'd love to see something like this in Mustang.
JMenuItem myMenu = new JMenuItem(bundle, "file.open");
. . .
file.open=&Open
file.open.accel=@O
# & shows accelerator, like in you-know-where, @ is menu shortcut key
Posted by: cayhorstmann on November 17, 2005 at 06:35 AM
-
[cayhorstman] I completely agree with you. I've also written
a system for extracting Actions from a resource bundle, see
Asserting Control Over the GUI: Commands, Defaults, and Resource Bundles
. My version of the boilerplate didn't encompass a revision
to the Swing component classes (but that's a good idea):
public class MyCommands {
public void quit() { ... }
}
AppDefaults defaults = new AppDefaults();
defaults.addResourceBundle("MyApplication");
...
MyCommands commands = new MyCommands();
Command quitCommand = new Command("quit", commands, defaults);
myMenu.add(quitCommand); // invokes commands.quit();
Posted by: hansmuller on November 17, 2005 at 09:16 AM
-
A couple of years ago I blogged about a case where in Java 1.4.x, the "easy" way to do it was replaced (through deprecation) with one that added considerable unnecessary complexity:
JComponent's "requestDefaultFocus()" was deprecated in 1.4(.1). So in order to do
tree.requestDefaultFocus();
you have to do
tree.getParent().getFocusTraversalPolicy().getDefaultComponent(tree.getParent()).requestFocus();
which is almost exactly what requestDefaultFocus() already did. I wrote, "If what you have works and is what you want the user to do most of the time anyways, why take it away from the user?"
Posted by: acroyear on November 17, 2005 at 02:29 PM
-
I have something for the hall of shame... and I think it has your name on it Hans.
Its the DefaultListModel (and perhaps other Swing models). In the JavaDocs you will see:
This class loosely implements the java.util.Vector
API, in that it implements the 1.1.x version of
java.util.Vector, has no collection class support,
and notifies the ListDataListeners when changes occur.
Presently it delegates to a Vector,
in a future release it will be a real Collection implementation.
...
version 1.35 05/05/04
author Hans Muller
--------------
Well I have to wonder why it isn't yet a real Collection implementation. I have made my own List Model class that implements java.util.List and javax.swing.ListModel so I can easily move data between my application logic and my UI. But that is something so basic and fundamental that it should be the way things are implemented by the standard JRE.
It looks like it wouldn't be particularly difficult to finish off.
Posted by: swpalmer on November 24, 2005 at 06:02 PM
|