I am trying to capture the very first moment when a component is shown on the screen without using \'dirty\' solutions as with use of a timer. Basically, I want to know the
I"ve use an AncestorListener and handled the ancestorAdded event.
For most purposes you can go with the first call to ComponentListener.componentMoved
(or if you are also interested in the size ComponentListener.componentResized
). Those are called whenever the position/size of the component changes.
Oddly, the ComponentListener
works just fine when applied to the JFrame
. Here is the altered source where I saw it work.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CompListenerTest
{
static ComponentListener cL = new ComponentAdapter()
{
@Override
public void componentShown(ComponentEvent e)
{
super.componentShown(e);
System.out.println("componentShown");
}
};
public static void main(String[] args)
{
JPanel p = new JPanel();
p.setPreferredSize(new Dimension(300, 400));
p.setBackground(Color.GREEN);
System.out.println("initial test p="+p.isShowing());
JPanel contentPane = new JPanel();
contentPane.setBackground(Color.RED);
contentPane.add(p);
JFrame f = new JFrame();
f.addComponentListener(cL);
f.setContentPane(contentPane);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
The reason a ComponentListener doesn't work is that it reports changes to the visible property - and that is true by default, even without being part of the component hierarchy.
To be reliably notified, use a HierarchyListener
First: the question as asked in the subject is not necessarily related to the actual problem (as commented by Boro below - any way to link to a comment?): there's no need to keep some kind of local flag to decide whether or not it is safe to send a getLocationOnScreen to a component, simply ask the component itself. Learn-item 1 for myself :-)
Second: The question as asked is quite interesting. Five experts (including myself, self-proclaimed), five different answers. Which triggered a bit of digging on my part.
My hypothesis: ComponentEvents are not useful for notification of (first-)showing. I knew that componentShown is useless because it's a kind-of propertyChange notification of the visible property of a component (which rarely changes). Puzzled about the suggested usefulness of moved/resized, though.
Constructing a use-case: fully prepare the frame in the example and keep it ready for later showing, a typical approach to improve perceived performance. My prediction - based on my hypothesis: resized/moved fired at prepare-time, nothing at show-time (note: the isShowing is what we are after, that is the latter). A snippet to add in the OP's example:
final JFrame f = new JFrame();
f.setContentPane(contentPane);
f.setSize(800, 600);
// f.pack();
JFrame controller = new JFrame("opener");
controller.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Action open = new AbstractAction("open/hide second") {
@Override
public void actionPerformed(ActionEvent e) {
f.setVisible(!f.isVisible());
}
};
controller.add(new JButton(open));
controller.pack();
controller.setVisible(true);
Disappointment: no notification at prepare-time, notification at show-time, just as needed, my hypothesis seemed wrong ;-) Last chance: swap the setSize for a pack ... and voila, notification at prepare-time, no notification at show-time, happy me again. Playing a bit more: looks like ComponentEvents are fired if the a component is displayable, which may or may not be useful in some contexts but not if showing is the state we are after. The
New imperial rules (draft):
Do not use ComponentListener for notification of "showing". That's left-over from AWT-age.
Do use AncestorListener. That seems to be the Swing replacement, slightly misnomed notification of "added" which actually means "showing"
Do use HierarchyListener only if really interested in fine-grained state changes
Using AncestorListener and ancestorAdded worked for me. Here's sample code:
addAncestorListener(new AncestorListener() {
@Override
public void ancestorRemoved(AncestorEvent event) {}
@Override
public void ancestorMoved(AncestorEvent event) {}
@Override
public void ancestorAdded(AncestorEvent event) {
// component is shown here
}
});
static ComponentListener cL = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
super.componentResized(e);
System.out.println("componentShown = "+e.getComponent().isDisplayable());
}
};