Is there a way to get notification when JComponent is not visible anymore beucase parent is removed from collection?

StackOverflow https://stackoverflow.com/questions/14833368

Question

Say I have a simple JFrame with a JTabbedPane containing 3 panels, and the second panel contains a JComponent. Is there a way the JComponent to be notified when "Tab 2" panel is removed from its container? My problem is that the JComponent may be deep in the hierarchy.

Obviously, I am looking for SWING solution here... :)

 ,'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''`.
 |                                                                |
 | ,----------Y.......................                            |
 | | Tab 1    | Tab 2     | Tab 3    |                            |
 | :..........:           :.....................................  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |               +--------------------+                      |  |
 | |               |Some JComponent here|                      |  |
 | |               +--------------------+                      |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | |                                                           |  |
 | `-----------------------------------------------------------'  |
 `-----------------------------------------------------------------

I tried to do this with the ancestorRemoved(), but no luck... I am obviously doing something wrong...

PS. the ASCII art is made with JavE.

Was it helpful?

Solution

I'd be use ComponentListener for this job (CardLayout is so close, similar in compare with JTabbedPane), code example contains all related listeners (disclaimer --> notice blocking EDT works only as code example and in this form only)

Ancesor & HierarchyListener are little bit asynchronous, maybe there is your problem to catch proper event from AncestorListener

my question about similar issue

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

public class CardlayoutTest extends JFrame {

    private static final long serialVersionUID = 1L;
    public CardLayout card = new CardLayout();

    public CardlayoutTest() {
        JPanel pnlA = new JPanel(new BorderLayout());
        pnlA.add(new JButton("A"), BorderLayout.CENTER);
        JPanel pnlB = new JPanel(new BorderLayout());
        pnlB.add(new JButton("B"), BorderLayout.CENTER);
        JPanel pnlC = new JPanel(new BorderLayout());
        pnlC.add(new JButton("C"), BorderLayout.CENTER);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(card);
        add(pnlA, "A");
        add(pnlB, "B");
        add(pnlC, "C");

        pnlA.addAncestorListener(new EventHandler());
        pnlB.addAncestorListener(new EventHandler());
        pnlC.addAncestorListener(new EventHandler());

        pnlA.addHierarchyListener(new EventHandler());
        pnlB.addHierarchyListener(new EventHandler());
        pnlB.addHierarchyListener(new EventHandler());

        pnlA.addComponentListener(new EventHandler());
        pnlB.addComponentListener(new EventHandler());
        pnlB.addComponentListener(new EventHandler());
    }

    class EventHandler implements AncestorListener, ComponentListener, HierarchyListener {

        @Override
        public void ancestorAdded(AncestorEvent event) {
            System.out.println("CardlayoutTest.EventHandler.ancestorAdded()");
        }

        @Override
        public void ancestorMoved(AncestorEvent event) {
            System.out.println("CardlayoutTest.EventHandler.ancestorMoved()");
        }

        @Override
        public void ancestorRemoved(AncestorEvent event) {
            System.out.println("CardlayoutTest.EventHandler.ancestorRemoved()");
        }

        @Override
        public void hierarchyChanged(HierarchyEvent e) {
            System.out.println("Components Change: " + e.getChanged());
            if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
                if (e.getComponent().isDisplayable()) {
                    System.out.println("Components DISPLAYABILITY_CHANGED : " + e.getChanged());
                } else {
                    System.out.println("Components DISPLAYABILITY_CHANGED : " + e.getChanged());
                }
            }
            if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
                if (e.getComponent().isDisplayable()) {
                    System.out.println("Components SHOWING_CHANGED : " + e.getChanged());
                } else {
                    System.out.println("Components SHOWING_CHANGED : " + e.getChanged());
                }
            }
        }

        public void componentHidden(ComponentEvent e) {
            System.out.println(e.getComponent().getClass().getName() + " --- Hidden");
        }

        public void componentMoved(ComponentEvent e) {
            System.out.println(e.getComponent().getClass().getName() + " --- Moved");
        }

        public void componentResized(ComponentEvent e) {
            System.out.println(e.getComponent().getClass().getName() + " --- Resized ");
        }

        public void componentShown(ComponentEvent e) {
            System.out.println(e.getComponent().getClass().getName() + " --- Shown");
        }
    }

    public static void main(String[] args) {
        CardlayoutTest t = new CardlayoutTest();
        t.setSize(500, 500);
        System.out.println("CardlayoutTest.main()------------------------ FIRST");
        t.card.show(t.getContentPane(), "A");
        t.setVisible(true);
        System.out.print("\n");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }
        System.out.println("CardlayoutTest.main()------------------------ SECOND");
        t.card.show(t.getContentPane(), "B");
        System.out.print("\n");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }
        System.out.println("CardlayoutTest.main()------------------------ THIRD");
        t.card.show(t.getContentPane(), "C");
        System.out.print("\n");
    }
}

OTHER TIPS

If it is the component itself which needs to do something, you can also override add/removeNotify (don't forget to call super though.

import java.awt.*;
import java.awt.event.*;
import java.util.Date;
import javax.swing.*;

public class LabelWithTimer extends JLabel {

    private final Timer timer;

    public LabelWithTimer() {
        timer = new Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setText(new Date().toString());
            }
        });
    }

    @Override
    public void addNotify() {
        super.addNotify();
        timer.start();
        System.out.println("Clock started");
    }

    @Override
    public void removeNotify() {
        System.out.println("Clock stopped");
        timer.stop();
        super.removeNotify();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                final JPanel clock = new JPanel(new GridBagLayout());
                clock.add(new LabelWithTimer());

                final JTabbedPane tabs = new JTabbedPane();
                tabs.addTab("Empty", new JLabel());
                tabs.setPreferredSize(new Dimension(400, 300));

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(tabs);
                frame.getContentPane().add(new JButton(new AbstractAction("Add/remove clock") {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if(tabs.getTabCount() == 2) {
                            tabs.removeTabAt(1);
                        }
                        else {
                            tabs.addTab("Clock", clock);
                            tabs.setSelectedIndex(1);
                        }

                    }
                }), BorderLayout.PAGE_END);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

Thanks to mKorbel's who posted a good example where he uses the HierarchyListener I came up with solution that I believe is satisfying my needs. In this case I use JLabel as the component that needs to be notified.

I will accept mKorbel's answer simply because he saved me lots of time.

Here is the code:

import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.JTabbedPane;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;

public class TestFrame extends JFrame {
    private static final long serialVersionUID = 8388031406846751884L;
    private JPanel contentPane;
    private JTabbedPane tabbedPane;
    private JLabel label; /// The component that needs to be notified when it should save its states.

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    TestFrame frame = new TestFrame();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Adds necessary listeners.
     * @param argLabel
     */
    private void install(JLabel argLabel) {
        // ::::: HierarchyListener ::::::::::::::::::::::::::::::::::::::::::::::::::::::::
        argLabel.addHierarchyListener(new HierarchyListener() {
            @Override
            public void hierarchyChanged(HierarchyEvent arg0) {
                if ((arg0.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
                    if (!arg0.getComponent().isDisplayable()) {
                        // component is not displayable due to the panel being removed
                        doSomething();
                    }
                }
            } 
        });

        // ::::: AncestorListener :::::::::::::::::::::::::::::::::::::::::::::::::::::::::
        argLabel.addAncestorListener(new AncestorListener() {

            @Override
            public void ancestorAdded(AncestorEvent arg0) {
                // not interested in this one
            }

            @Override
            public void ancestorMoved(AncestorEvent arg0) {
                // not interested in this one
            }

            @Override
            public void ancestorRemoved(AncestorEvent arg0) {
                // ancestorRemoved() is useful when user navigates between tabs, and causes
                // component to become invisible.
                doSomething();
            } 
        });
    }

    public void doSomething() {
        System.out.println("Saving some state(s)...");
    }

    /**
     * Create the frame.
     */
    public TestFrame() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 693, 376);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);

        tabbedPane = new JTabbedPane(JTabbedPane.TOP);
        // first panel
        tabbedPane.add(new JPanel()); 
        // second panel
        JPanel panelWithComponents = new JPanel();
        JPanel childPanel = new JPanel();
        label = new JLabel("Test label");
        install(label);
        childPanel.add(label);
        panelWithComponents.add(childPanel);
        tabbedPane.add(childPanel);
        // third panel
        tabbedPane.add(new JPanel());

        contentPane.add(tabbedPane, BorderLayout.CENTER);

        JButton btnRemove = new JButton("Remove second");
        btnRemove.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                tabbedPane.remove(1);
            }
        });
        contentPane.add(btnRemove, BorderLayout.SOUTH);
    } // main() method

} // TestFrame class
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top