JLayeredPane with a LayoutManager

2019-02-24 11:03发布

问题:

The situation: drawing a stack of playing cards, like in the Solitaire game. Nicely stacked.

To achieve this, I'm using a JLayeredPane in combination with a custom implementation of the LayoutManager interface. The reason for using a custom LayoutManager is that the stack orientation varies, sometimes the playing cards cover each other completely, sometimes partially, and this logic seems to be a good job for a LayoutManager, because this basically boils down to setting the location of the cards.

So, the LayoutManager is responsible for setting the X- and Y-coordinates of all components in my stack. The JLayeredPane on the other hand is responsible for their Z-coordinates (via its layers).

Adding a component to a JLayeredPane goes like this:

JLayeredPane pane = new JLayeredPane();
pane.setLayout(new StackLayout(...));
pane.add(new CardView(...), new Integer(j));

where new Integer(j) is the layer of the card. This must be an Integer due to the contract of JLayeredPane.

The problem here is, that my StackLayout cannot have any other constraint object than an Integer, due to the reason stated above. The LayoutManager interface requires you to implement the following method:

addLayoutComponent(Component comp, Object constraints);

and the passed Object will here always be an Integer.

In my particular situation, I am lucky, as my XY-coordinates can be calculated based on the Z-coordinates. For example, the card in layer k has to be located at Y-coordinate k * offset. So in my situation, the constraints object being an Integer is not a problem.

I was wondering what you should be doing when there is no correlation between the Z-coordinates and the XY-coordinates? How can you solve this then? For example, how would I use a GridBagLayout in combination with a JLayeredPane, where the first requires a GridBagConstraints object and the second an Integer object? Of course, a GBL will layout in such a way that components do not overlap, but it's just the idea.

回答1:

Based on the comment of Hovercraft Full Of Eels, I'll answer my own question.

Calling JLayeredPane.add(Component comp, Object constraints) will call addImpl(Component comp, Object constraints, int index). This method is overridden by JLayeredPane itself, here's the source:

protected void addImpl(Component comp, Object constraints, int index) {
    int layer;
    int pos;

    if(constraints instanceof Integer) {
        layer = ((Integer)constraints).intValue();
        setLayer(comp, layer);
    } else
        layer = getLayer(comp);

    pos = insertIndexForLayer(layer, index);
    super.addImpl(comp, constraints, pos);
    comp.validate();
    comp.repaint();
    validateOptimizedDrawing();
}

As you can see, the Integer object is intercepted, so that the JLayeredPane knows the layer in which comp should be placed. Then, it passes on constraints, the Integer, to the super implementation. The essential part in the super implementation, Container, is:

protected void addImpl(Component comp, Object constraints, int index) {
    ...
    if (layoutMgr != null) {
        if (layoutMgr instanceof LayoutManager2) {
           ((LayoutManager2)layoutMgr).addLayoutComponent(comp, constraints);
        } else if (constraints instanceof String) {
           layoutMgr.addLayoutComponent((String)constraints, comp);
        }
    }
    ...
}

The solution would therefore be to extend JLayeredPane and override its addImpl(Component comp, Object constraints, int index) method, which can accept any Object of your choice: this object should contain the Integer needed for the JLayeredPane's layer decision, and it should also contain the constraints object for the chosen LayoutManager.

StackConstraints:

public final class StackConstraints {
    public final int layer;
    public final Object layoutConstraints;
    public StackConstraints (int layer, Object layoutConstraints){
        this.layer = layer;
        this.layoutConstraints = layoutConstraints;
    }
}

JLayeredPane extension:

protected void addImpl(Component comp, Object constraints, int index) {
    int layer;
    int pos;
    Object constr;
     if(constraints instanceof StackConstraints) {
        layer = constraints.layer.intValue();
        constr = ((StackConstraints) constraints).layoutConstraints;
        setLayer(comp, layer);
    } else {
        layer = getLayer(comp);
        constr = constraints;
    }

    pos = insertIndexForLayer(layer, index);
    super.addImpl(comp, constraints, pos);
    comp.validate();
    comp.repaint();
    validateOptimizedDrawing();

}



回答2:

So, the LayoutManager is responsible for setting the X- and Y-coordinates of all components in my stack. The JLayeredPane on the other hand is responsible for their Z-coordinates (via its layers).

You don't need to use a layered pane for this. A panel also supports Z-Order. Check out the Overlap Layout which explains more about this and may do what you need.