Skip to main content

Dialog Diatribe

Posted by hansmuller on October 27, 2006 at 5:48 PM PDT

I've been writing the occasional small application recently and
now and then I blunder into a problem with Java SE that's,
uh..., well, annoying. I realize that I'm not the only one
who's had this experience and I'm probably not the only one who
seeks relief by writing a lengthy diatribe and then sending it
to whomever might be guilty of creating the situation. Of
course, in my case that's often me, and since relief usually
doesn't come from berating oneself, I'm guilty of sending the
occasional long crabby missive to the people who are currently
responsible for maintaining things that I'm probably responsible
for bollocksing up in the first place. It's not a particularly
endearing habit.

I sent the following to Swing's technical lead,
Shannon
Hickey
, and he confirmed that the details, though twisted
with bile, are essentially correct. So in the interest of
furthering my own therapy, and also to ensure that some record of
this will be stored away in Google's indices till the end
of time, I thought I'd share.

I would think that a fairly common idiom in a Swing application
would be to popup a dialog in response to selecting a menu item.
Given Matisse, we'll
assume that the JDialog has been created with the IDE, rather
than some JOptionPane convenience method, and given rudimentary
aesthetics, assume the dialog should be centered over the menu
item's frame. Accomplishing this seems to be much too
difficult:

public void showMyDialog(ActionEvent e) {
    // How to find the Dialog's Frame owner?
    Window dialogOwner = null;
    JDialog dialog = new MyDialog(dialogOwner, true); // true => modal Dialog
    dialog.pack();
    // How to center the Dialog?
    dailog.setVisible(true);
}

The first problem to deal with is mapping from the menu item's
ActionEvent to the frame that contains the menu item. The
frame will be the dialog's owner as well as the component we're
going to center the dialog relative to.

There seems to be an overabundance of SwingUtilities methods that
address this trivial problem:

  • Window getWindowAncestor(Component c)
  • Window windowForComponent(Component c)
  • Component getRoot(Component c)

Sadly, none of them "work" for a JMenuItem. They all simply
traipse up the parent chain and in our case they find a
JPopupMenu and then null.

To find the Frame that owns a JMenuItem, we have to follow the JPopupMenu's

"invoker"
property, which gets us back into the component
hierarchy. So to find the frame that corresponds to an ActionEvent
one must write (!):

Frame frameForActionEvent(ActionEvent e) {
    if (e.getSource() instanceof Component) {
        Component c = (Component)e.getSource();
        while(c != null) {
            if (c instanceof Frame) {
        return (Frame)c;
            }
            c = (c instanceof JPopupMenu) ? ((JPopupMenu)c).getInvoker() : c.getParent();
        }
    }
    return null;
}

It would more useful to have written
windowForActionEvent but sadly support for
creating Dialogs whose owner is a Window (the parent class for
Frames and Dialogs) only appeared in Java SE 6, and I needed
code that worked for Java SE 5. It's also worth noting that
this works for Applets too, although you'd be forgiven for not
guessing that this is true. Applets do have a Frame parent
that's created by the Java plugin and whose bounds are the same
as the Applet (Panel) itself.

But we're still not done, because we must also center the dialog
over the frame. Naturally there are other useful positions for
the dialog. Centering a dialog over its frame happens to be what
started me on this quest.

I have it on good authority that
href="http://download.java.net/jdk6/docs/api/java/awt/Window.html#setLocationRelativeTo(java.awt.Component)">
Windows.setLocationRelativeTo() is the handy method for
this job. The javadoc for this method isn't promising:

    Sets the location of the window relative to the specified
    component.
 

OK so far. Except it sounds like I'm going to have to compute the
relative origin of my dialog and deal with edge (of the screen)
conditions. Yech.

    If the component is not currently showing, or c is null, the
    window is placed at the center of the screen. The center point
    can be determined with GraphicsEnvironment.getCenterPoint
    [sic]
 

Huh? What does "the component" refer to in this sentence? I
assume they're not referring to this Window and I have to
wonder what "showing" means in this context. Is is the same
thing as getVisible() being true? If not, do I
have to hope that my menu item is still "showing" when this
method is called? And what's this advice about
getCenterPoint (and where's the period)? In my
case the Window and its menu item are visible, so I'm hoping
none of this stuff applies. Because I don't really understand
it.

    If the bottom of the component is offscreen, the window is
    placed to the side of the Component that is closest to the
    center of the screen. So if the Component is on the right part
    of the screen, the Window is placed to its left, and visa
    versa.
 

This, no doubt, means that the method will endeavor to find a
location for my dialog that respects the relative location I've
specified, without making part of the dialog appear off-screen.
Good, I think.

So I still appear to be stuck with computing an origin for my
dialog that centers it relative to its owner. Before I code
that, I try leaving the origin of the new dialog at 0,0, which
is the default:

public void showMyDialog(ActionEvent e) {
    Window dialogOwner = frameForActionEvent(e);
    JDialog dialog = new MyDialog(dialogOwner, true);
    dialog.pack();
    aboutBox.setLocationRelativeTo(dialogOwner);
    dialog.setVisible(true);
}

Miraculously, this works. The dialog appears centered over the
dialogOwner unless that would cause the dialog to appear
off-screen. I have no idea why it works, since according to the
"spec" (and the name of the method) I should have had to compute
an appropriate relative origin for the dialog. But I guess I
don't.

Frankly, I think this whole mess is a mini-travesty. If I'm
going to show a dialog, I should be able to do so without
writing code that digs around the component hierarchy and
without experimentally determining what something as simple (and
not terribly useful) as Window.setLocationRelativeTo() does.

There, that feels a little better.

A href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4231737">
seven year old bug that covers the menu item to frame lookup
problem is still open. Given the fact that it's accumulated
exactly 0 votes in that time, perhaps no one has ever cared about
the problem quite as much as I do at this moment. I would think
that a cleaner way to handle this case would be some static
methods that handled the entire idiom, for example:

public void showMyDialog(AWTEvent event) {
    Window dialogOwner = Window.eventToWindow(event);
    JDialog dialog = new MyDialog(dialogOwner, true);
    Window.showModalDialog(dialog); // Center dialog relative to its owner
}

And Shannon suggested that the method name might be rationalized
as implying that the Window is be moved to a location that makes
its relationship to the component parameter obvious.
Typically that means centering the Window relative to the
component. In return for that tortured explanation, I had to
agree to file an RFE about the
Window.setLocationRelativeTo() javadoc. I haven't
done so yet. But I will.

Thanks for listening.

Related Topics >>