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();
}
}
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());
}
}