Search code examples
javaswingjpanellistenerjcomponent

Listening for descendant component hierarchy changes


Is there a way for a JComponent to get notification of add/removed changes in its descendant component hierarchy?

For example, in the code below there is an addChild button that will add a new child JPanel to the root or the last panel added. I would like the root panel to get a notification of this. Sort of like a HierarchyListener but the other way round or a ContainerListener that listened for more than just the immediate children.

public class DecendantHierarchyListening extends JFrame {

    private final JPanel root = createChildPanel(null);

    public JPanel leafComponent = root;

    public DecendantHierarchyListening() {
        super("title");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JComponent buttons = new JPanel();

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(createAddChildButton(buttons), BorderLayout.NORTH);
        panel.add(root, BorderLayout.CENTER);

        getContentPane().add(panel);
    }

    private Button createAddChildButton(JComponent buttons) {
        Button button = new Button("AddChild");
        buttons.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                leafComponent = (JPanel) leafComponent
                        .add(createChildPanel(leafComponent));
                DecendantHierarchyListening.this.invalidate();
                DecendantHierarchyListening.this.validate();
                DecendantHierarchyListening.this.repaint();
            }
        });
        return button;
    }

    public static JPanel createChildPanel(Container parent) {
        Color[] colors = new Color[] { Color.RED, Color.BLUE, Color.GREEN };
        JPanel panel = new JPanel(new BorderLayout());
        panel.setPreferredSize(new Dimension(200, 200));
        Color color;
        if (parent == null) {
            color = Color.GREEN;
        } else {
            int distance = 1;
            parent = parent.getParent();
            while (parent != null) {
                distance++;
                parent = parent.getParent();
            }

            color = colors[distance % colors.length];
        }

        panel.setBorder(BorderFactory.createLineBorder(color, 2));
        return panel;
    }

    public static void runDemo() {
        JFrame f = new DecendantHeirarchyListening();
        f.pack();
        f.setVisible(true);
    }

    public static void main(String[] args) {
        DecendantHierarchyListening.runDemo();
    }
}

Solution

  • One possible solution would be to use a single ContainerListener that adds itself to the containers that are added to the "root" container (and, just for completeness, also removes itself from the containers that are removed, for the case that this is necessary).

    So you basically can create a ContainerListener and add it to the root container. Whenever this listener is notified about a new child component that is added, it adds itself to this component as well (if it is a container). Later, when a comonent is added to the child component, then the listener will be informed, and may add itself again to the grandchild, and so on.

    I sketched it here in an example:

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import java.awt.Container;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.ContainerEvent;
    import java.awt.event.ContainerListener;
    
    import javax.swing.BorderFactory;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class DescendantHierarchyListening extends JPanel
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    createAndShowGUI();
                }
            });
        }
    
        public static void createAndShowGUI()
        {
            JFrame f = new JFrame();
            f.add(new DescendantHierarchyListening());
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setSize(400, 400);
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    
        ContainerListener containerListener = new ContainerListener()
        {
            @Override
            public void componentAdded(ContainerEvent e)
            {
                Component child = e.getChild();
                System.out.println("Added   " + child);
                System.out.println("   to   " + e.getContainer());
    
                if (child instanceof Container)
                {
                    Container container = (Container)child;
                    container.addContainerListener(this);
                }
            }
    
            @Override
            public void componentRemoved(ContainerEvent e)
            {
                Component child = e.getChild();
                System.out.println("Removed " + child);
                System.out.println("   from " + e.getContainer());
                if (child instanceof Container)
                {
                    Container container = (Container)child;
                    container.removeContainerListener(this);
                }
            }
        };
    
        private final JPanel root;
        public JPanel leafComponent;
    
        public DescendantHierarchyListening()
        {
            super(new BorderLayout());
    
            JButton button = createAddChildButton();
            add(button, BorderLayout.NORTH);
    
            root = createChildPanel(null);
            root.addContainerListener(containerListener);
    
            add(root, BorderLayout.CENTER);
            leafComponent = root;
        }
    
        private JButton createAddChildButton()
        {
            JButton button = new JButton("AddChild");
            button.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    JPanel child = createChildPanel(leafComponent);
                    leafComponent.add(child);
                    leafComponent = child;
                    revalidate();
                }
            });
            return button;
        }
    
        public JPanel createChildPanel(final Container parent)
        {
            JPanel panel = new JPanel(new BorderLayout())
            {
                @Override
                public String toString()
                {
                    return "Child of " + parent;
                }
            };
            Color color = getColor(parent);
            panel.setBorder(BorderFactory.createLineBorder(color, 2));
            return panel;
        }
    
        private static Color getColor(Component c)
        {
            if (c == null)
            {
                return Color.GREEN;
            }
            Color[] colors = new Color[]
            { Color.RED, Color.BLUE, Color.GREEN };
            int d = getDepth(c);
            return colors[d % colors.length];
        }
    
        private static int getDepth(Component c)
        {
            if (c == null)
            {
                return 0;
            }
            return 1 + getDepth(c.getParent());
        }
    
    }