Say I have two components, A and B, in a JPanel. I want Component A to stay left aligned, while Component B does its best to stay in the middle of the panel. I mocked up the following demo (sorry for the quality, I made it in paint):
What I am doing now is using a GridBagLayout on the JPanel, and keeping A left aligned while keeping B centered, but B stays centered within the 2nd column, so it is centered in the space remaining after A is placed, instead of centered with respect to the panel as a whole.
I cannot use any 3rd party libraries for this. Is there a way to do this using pure Swing?
Firefly's answer is correct (as marked) but I created an SSCCE showing my original problem (first row), Hovercraft Full Of Eels' attempted solution (second row), and the Firefly's correct solution (third row). I figured it can't hurt to post it:
package stackoverflow;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.LayoutManager2;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class StackOverflowTest extends JFrame
{
public StackOverflowTest()
{
super("Stack Overflow Test");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel testPanel = new JPanel(new GridLayout(3, 1));
// set up grid bag layout example
JPanel gridBagPanel = new JPanel(new GridBagLayout());
GridBagConstraints gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = GridBagConstraints.LINE_START;
gridBagPanel.add(getA(), gridBagConstraints);
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = GridBagConstraints.CENTER;
gridBagConstraints.weightx = 1.0;
gridBagPanel.add(getB(), gridBagConstraints);
testPanel.add(gridBagPanel);
// set up border layout panel
JPanel borderPanel = new JPanel(new BorderLayout());
borderPanel.add(getA(), BorderLayout.LINE_START);
JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
borderPanel.add(flowPanel, BorderLayout.CENTER);
flowPanel.add(getB());
testPanel.add(borderPanel);
// set up sly493 layout panel
JPanel sly493LayoutPanel = new JPanel(new Sly493LayoutManager());
sly493LayoutPanel.add(getA(), Sly493LayoutManager.LEFT);
sly493LayoutPanel.add(getB(), Sly493LayoutManager.CENTERED);
testPanel.add(sly493LayoutPanel);
// set up panel to act as the midpoint marker
JPanel midpointMarkerPanel = new JPanel()
{
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
int w = getWidth();
int h = getHeight();
int x = w / 2;
g2.drawLine(x, 0, x, h);
g2.drawLine(x, 0, x - (h / 5), (h / 5));
g2.drawLine(x, 0, x + (h / 5), (h / 5));
}
};
midpointMarkerPanel.setPreferredSize(new Dimension(1, 50));
// setup up content pane
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.add(testPanel, BorderLayout.NORTH);
contentPane.add(midpointMarkerPanel, BorderLayout.CENTER);
this.setContentPane(contentPane);
pack();
}
private JPanel getA()
{
JPanel aPanel = new JPanel(new BorderLayout());
JLabel aLabel = new JLabel("A", JLabel.CENTER);
aLabel.setFont(aLabel.getFont().deriveFont(Font.BOLD, 36));
aPanel.add(aLabel, BorderLayout.CENTER);
aPanel.setBackground(Color.RED);
aPanel.setPreferredSize(new Dimension(50, 50));
return aPanel;
}
private JPanel getB()
{
JPanel bPanel = new JPanel();
JLabel bLabel = new JLabel("B", JLabel.CENTER);
bLabel.setForeground(Color.WHITE);
bLabel.setFont(bLabel.getFont().deriveFont(Font.BOLD, 36));
bPanel.add(bLabel, BorderLayout.CENTER);
bPanel.setBackground(Color.BLUE);
bPanel.setPreferredSize(new Dimension(50, 50));
return bPanel;
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
new StackOverflowTest().setVisible(true);
}
});
}
private static class Sly493LayoutManager implements LayoutManager2
{
public static final Integer LEFT = 0;
public static final Integer CENTERED = 1;
private Component leftComponent;
private Component centeredComponent;
@Override
public void addLayoutComponent(String name, Component comp)
{
}
@Override
public void removeLayoutComponent(Component comp)
{
if (leftComponent == comp)
{
leftComponent = null;
}
else if (centeredComponent == comp)
{
centeredComponent = null;
}
}
@Override
public Dimension preferredLayoutSize(Container parent)
{
Dimension d = new Dimension();
for (Component c : parent.getComponents())
{
//wide enough to stack the left and center components horizontally without overlap
d.width += c.getPreferredSize().width;
//tall enough to fit the tallest component
d.height = Math.max(d.height, c.getPreferredSize().height);
}
return d;
}
@Override
public Dimension minimumLayoutSize(Container parent)
{
return preferredLayoutSize(parent);
}
@Override
public void layoutContainer(Container parent)
{
//in this method we will:
//1) position the left component on the left edge of the parent and center it vertically
//2) position the center component in the center of the parent (as long as it would not overlap
//the left component) and center it vertically
int leftComponentWidth = leftComponent.getPreferredSize().width;
int leftComponentHeight = leftComponent.getPreferredSize().height;
int centeredComponentWidth = centeredComponent.getPreferredSize().width;
int centeredComponentHeight = centeredComponent.getPreferredSize().height;
leftComponent.setBounds(0, (parent.getHeight() - leftComponentHeight) / 2, leftComponentWidth, leftComponentHeight);
int leftComponentRightEdge = leftComponent.getX() + leftComponent.getWidth();
int centerComponentLeftEdge = (parent.getWidth() - centeredComponentWidth) / 2;
int centerComponentTopEdge = (parent.getHeight() - centeredComponentHeight) / 2;
if (leftComponentRightEdge >= centerComponentLeftEdge)
{
//Center component will "do its best" to remain in the center
//but it will not do so if it would cause it to overlap the left component
centerComponentLeftEdge = leftComponentRightEdge;
}
centeredComponent.setBounds(centerComponentLeftEdge,
centerComponentTopEdge,
centeredComponentWidth,
centeredComponentHeight);
}
@Override
public void addLayoutComponent(Component comp, Object constraints)
{
if (LEFT.equals(constraints))
{
if (leftComponent != null)
{
throw new IllegalStateException("A left component has already been assigned to this layout.");
}
leftComponent = comp;
}
else if (CENTERED.equals(constraints))
{
if (centeredComponent != null)
{
throw new IllegalStateException("A centered component has already been assigned to this layout.");
}
centeredComponent = comp;
}
else
{
throw new IllegalStateException("Unexpected constraints '" + constraints + "'.");
}
}
@Override
public Dimension maximumLayoutSize(Container target)
{
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@Override
public float getLayoutAlignmentX(Container target)
{
return 0;
}
@Override
public float getLayoutAlignmentY(Container target)
{
return 0;
}
@Override
public void invalidateLayout(Container target)
{
}
}
}
If I'm understanding your needs correctly, you want B centered relative to the parent as a whole, not centered in the space left over after A is positioned. That makes this problem interesting and after testing the other suggested answers, I don't believe they can meet that requirement.
I'm having trouble thinking of a way to combine the built-in layout managers in a way that would achieve that. So, I've hacked up a custom implementation of LayoutManager2.
The following executable example may meet your needs. The implementation is quick and dirty and is in no way an example of a good generalized layout manager, but it appears to meet your requirements and behaves like your drawings made me think it should. I interpreted your requirement that "B does its best to stay in the middle of the panel" to mean that B should try to remain centered relative to the panel as a whole, but not at the expense of overlapping A.
package com.example;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager2;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Example {
public Example() {
JPanel a = new JPanel();
a.setBackground(Color.RED);
a.setPreferredSize(new Dimension(128, 128));
JPanel b = new JPanel();
b.setBackground(Color.BLUE);
b.setPreferredSize(new Dimension(128, 128));
JPanel panel = new JPanel(new Sly493LayoutManager());
panel.add(a, Sly493LayoutManager.LEFT);
panel.add(b, Sly493LayoutManager.CENTERED);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new Example();
}
private static class Sly493LayoutManager implements LayoutManager2 {
public static final Integer LEFT = 0;
public static final Integer CENTERED = 1;
private Component leftComponent;
private Component centeredComponent;
@Override
public void addLayoutComponent(String name, Component comp) { }
@Override
public void removeLayoutComponent(Component comp) {
if (leftComponent == comp) {
leftComponent = null;
} else if (centeredComponent == comp) {
centeredComponent = null;
}
}
@Override
public Dimension preferredLayoutSize(Container parent) {
Dimension d = new Dimension();
for (Component c : parent.getComponents()) {
//wide enough to stack the left and center components horizontally without overlap
d.width += c.getPreferredSize().width;
//tall enough to fit the tallest component
d.height = Math.max(d.height, c.getPreferredSize().height);
}
return d;
}
@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
@Override
public void layoutContainer(Container parent) {
//in this method we will:
//1) position the left component on the left edge of the parent and center it vertically
//2) position the center component in the center of the parent (as long as it would not overlap
//the left component) and center it vertically
int leftComponentWidth = leftComponent.getPreferredSize().width;
int leftComponentHeight = leftComponent.getPreferredSize().height;
int centeredComponentWidth = centeredComponent.getPreferredSize().width;
int centeredComponentHeight = centeredComponent.getPreferredSize().height;
leftComponent.setBounds(0, (parent.getHeight() - leftComponentHeight) / 2, leftComponentWidth, leftComponentHeight);
int leftComponentRightEdge = leftComponent.getX() + leftComponent.getWidth();
int centerComponentLeftEdge = (parent.getWidth() - centeredComponentWidth) / 2;
int centerComponentTopEdge = (parent.getHeight() - centeredComponentHeight) / 2;
if (leftComponentRightEdge >= centerComponentLeftEdge) {
//Center component will "do its best" to remain in the center
//but it will not do so if it would cause it to overlap the left component
centerComponentLeftEdge = leftComponentRightEdge;
}
centeredComponent.setBounds(centerComponentLeftEdge,
centerComponentTopEdge,
centeredComponentWidth,
centeredComponentHeight);
}
@Override
public void addLayoutComponent(Component comp, Object constraints) {
if (LEFT.equals(constraints)) {
if (leftComponent != null) {
throw new IllegalStateException("A left component has already been assigned to this layout.");
}
leftComponent = comp;
} else if (CENTERED.equals(constraints)) {
if (centeredComponent != null) {
throw new IllegalStateException("A centered component has already been assigned to this layout.");
}
centeredComponent = comp;
} else {
throw new IllegalStateException("Unexpected constraints '" + constraints + "'.");
}
}
@Override
public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@Override
public float getLayoutAlignmentX(Container target) {
return 0;
}
@Override
public float getLayoutAlignmentY(Container target) {
return 0;
}
@Override
public void invalidateLayout(Container target) {
}
}
}
I'm sad that it can't be done without a custom layout manager, but this is perfect.
Glad it works for you. Agree on the sadness... custom layout managers are rarely the best option. Would love to see an implementation based on the built-in managers, but I've got nothin' :(