Question

I noticed if you try to use LEADING alignment in GroupLayout when one of the components is a button and the other is not a button, the components no longer line up.

If both components are buttons, you get results like this:

getContainerGap(button, EAST, container) -> 14
getContainerGap(another button, EAST, container) -> 14
getContainerGap(button, WEST, container) -> 14
getContainerGap(another button, WEST, container) -> 14
getContainerGap(button, SOUTH, container) -> 17
getPreferredGap(another button, button, RELATED, SOUTH, container) -> 4
getContainerGap(another button, SOUTH, container) -> 17

result of layout when both are buttons

If you get out a ruler, you can see that the left margin is exactly 20 pixels, so this result is correct.

If one of the components is not a button (usually I'm seeing this when I try to align a button to the left edge of a JScrollPane or similar), you get results like this:

getContainerGap(button, EAST, container) -> 14
getContainerGap(reference component, EAST, container) -> 20
getContainerGap(button, WEST, container) -> 14
getContainerGap(reference component, WEST, container) -> 20
getContainerGap(button, SOUTH, container) -> 17
getPreferredGap(reference component, button, RELATED, SOUTH, container) -> 7
getContainerGap(reference component, SOUTH, container) -> 20

result of layout when one component is not a button

Now if you get out a ruler, the left margin of the red border is exactly 20 pixels, but the button juts out an additional 6 pixels.

The getVisualMargin method exists to correct this, and you can see that getContainerGap(button, EAST, container) returns 14, which is compensating for the 6 pixels of extra margin outside the button.

If you have more types of component to lay out, the results get progressively worse and you end up with a jagged edge all the way down the layout, since only similar components will align with each other.

I'm wondering what is going on here. Even though the LayoutStyle is returning the right value for the container gap, it seems like GroupLayout is throwing away the value and using the value from the other component, which guarantees that the layout will be wrong.

Test program source:

import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.LayoutStyle;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

public class ButtonVisualMarginTest implements Runnable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new ButtonVisualMarginTest());
    }

    @Override
    public void run() {
        LayoutStyle.setInstance(new AquaLayoutStyle());

        JComponent reference = new JComponent() {};
        reference.setName("reference component");
        reference.setPreferredSize(new Dimension(200, 50));
        reference.setMaximumSize(new Dimension(200, 50));
        reference.setBorder(BorderFactory.createLineBorder(Color.RED));

//        JButton reference = new JButton("Another button");
//        reference.setName("another button");
//        reference.setFocusable(false);

        JButton button = new JButton("Text only");
        button.setName("button");
        button.setFocusable(false);

        JPanel container = new JPanel();
        container.setName("container");
        GroupLayout layout = new GroupLayout(container);
        layout.setAutoCreateGaps(true);
        layout.setAutoCreateContainerGaps(true);
        container.setLayout(layout);
        container.setBackground(new Color(192, 192, 255));
        container.setOpaque(true);

        layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addComponent(reference)
            .addComponent(button));

        layout.setVerticalGroup(layout.createSequentialGroup()
            .addComponent(reference)
            .addComponent(button));

        JFrame frame = new JFrame("Test");
        frame.setLayout(new BorderLayout());
        frame.add(container, BorderLayout.CENTER);
        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);

    }
}

The slightly modified version of AquaLayoutStyle I'm trying to work with is here (it was too big to include.)

Because people might be curious, here is the result of not using AquaLayoutStyle:

result of not using AquaLayoutStyle

And here is the layout you get when using Xcode to lay out a button next to a scrollable component (this is what we should be aiming for if we want Java applications to look native.)

native layout of similar components

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top