Alignment of Single Characters in Java BoxLayout on Y-Axis Is Off-Center
Asked Answered
T

3

6

There seems to be an issue with aligning certain characters to the center of a BoxLayout along the y-axis in Java. I don't know what could cause this, & I've created an SSCCE to demonstrate the effect. In the example, I only use the character 'a', & I draw a line down the direct middle of each JPanel to demonstrate how far off each case is from the center. The case with bold text seems to line up fine, but normal formatting & italics are both grossly off-center, despite using both setAlignmentX & setHorizontalAlignment. Any help on understanding this effect is appreciated.

In the case that somehow the problem is with Java on my specific computer, this is an image of what displays on my screen when I run the SSCCE, which loads three different JPanels with BoxLayouts along the y-axis & puts a centered JLabel with only the character 'a' in each: enter image description here

& here is the code for the SSCCE:

import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;

public class AlignmentTest extends JPanel
{
    public AlignmentTest(char label, int style)
    {
        JLabel l = new JLabel(Character.toString(label));
        setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
        setBackground(Color.WHITE);
        setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
        setPreferredSize(new Dimension(300,50));
        add(Box.createVerticalGlue());
        add(l);
            l.setFont(l.getFont().deriveFont(style));
            l.setAlignmentX(CENTER_ALIGNMENT);
            l.setHorizontalAlignment(JLabel.CENTER);
        add(Box.createVerticalGlue());
    }
    public static void main(String[] args)
    {
        JFrame f = new JFrame("Alignment Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new GridLayout(1,0,5,5));
        f.add(new AlignmentTest('a',Font.PLAIN));
        f.add(new AlignmentTest('a',Font.BOLD));
        f.add(new AlignmentTest('a',Font.ITALIC));
        f.pack();
        f.setVisible(true);
    }
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawLine(getWidth()/2,0,getWidth()/2,getHeight());
    }
}
Taille answered 6/1, 2015 at 0:49 Comment(2)
For clarity, use style names.Interscholastic
Good point, I didn't think about that. I've changed the code to use style names now. Thank you for the input.Taille
D
4

Using JDK7 on Windows 7 none of the characters are center aligned.

I made some changes to display a JTextField and I played with the columns of the JTextField (1, 3, 5). As the columns increased the center aligned improved and was reasonable at columns 5 and above. So the problem is somehow related to the width of the component.

I would guess there is some weird rounding error in the layout. This seems like a bug to me.

In case you are interested in a layout that provides some similar functionality to the BoxLayout you can check out the Relative Layout. The changes to your example are minor:

import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;

public class AlignmentTest extends JPanel
{
    public AlignmentTest(char label, int style)
    {
        JLabel l = new JLabel(Character.toString(label));
        setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
        setBackground(Color.WHITE);
//        setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
        setLayout(new RelativeLayout(RelativeLayout.Y_AXIS));
        setPreferredSize(new Dimension(300,50));
//        add(Box.createVerticalGlue());
        add(Box.createVerticalGlue(), new Float(1));
        add(l);
            l.setFont(l.getFont().deriveFont(style));
            l.setAlignmentX(CENTER_ALIGNMENT);
            l.setHorizontalAlignment(JLabel.CENTER);
//        add(Box.createVerticalGlue());
        add(Box.createVerticalGlue(), new Float(1));
    }
    public static void main(String[] args)
    {
        JFrame f = new JFrame("Alignment Test");
        JScrollPane scroller = new JScrollPane();
            JPanel panel = new JPanel(new GridLayout(1,0,5,5));
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new GridLayout(1,0,5,5));
        f.add(new AlignmentTest('a',Font.PLAIN));
        f.add(new AlignmentTest('a',Font.BOLD));
        f.add(new AlignmentTest('a',Font.ITALIC));
        f.pack();
        f.setVisible(true);
    }
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawLine(getWidth()/2,0,getWidth()/2,getHeight());
    }
}
Downfall answered 6/1, 2015 at 3:51 Comment(3)
The RelativeLayout is definitely an adequate workaround. I haven't heard of it before, but it's something I could use here & in future applications. Though, I had to go online & copy the source code for RelativeLayout to my project in order to use it. I have Java version 1.7.0_25. Do I need to update in order to have the RelativeLayout class built into Java without me needing to copy the source code manually?Taille
@ScottHetrick, RelativeLayout is just some (unsupported) code that I wrote to provide the functionality as described in the link. There is no jar file or anything for it, so yes you use it like any class you write yourself. The class is not version dependent.Downfall
Oh, I gotcha. That's impressive. Thanks for the recommendation, I'll likely be using that in the future.Taille
S
5

Another way to avoid "Box Layout Features: … Any extra space appears at the right of the container", you would need to override the JLabel#getMinimumSize() method to return the same Dimension as JLabel#getPreferredSize().

Sorry, I misunderstood.

As @camickr has already said,

I would guess there is some weird rounding error in the layout. This seems like a bug to me.

is quite correct.

Fixed example:

//MinimumSize checkbox
//selected true: set min width = 100px
//selected false: set min width = 7px(default "a" width)
//Here's my attempt(I am running JDK 1.7.0_72 on Windows 7):
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

public class AlignmentTest4 extends JPanel {
  private static boolean FLAG = false;
  @Override public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
  }
  @Override public Dimension getPreferredSize() {
    return new Dimension(300, 80);
  }
  public static JLabel makeLabel(String label, int style) {
    JLabel l = new JLabel(label) {
      @Override public Dimension getPreferredSize() {
        return new Dimension(120, 30);
      }
      @Override public Dimension getMinimumSize() {
        Dimension d = super.getMinimumSize();
        if (FLAG) {
          d.width = 100;
        } else {
          d.width = 7;
        }
        return d;
        //if (FLAG) {
        //  return this.getPreferredSize();
        //} else {
        //  return super.getMinimumSize();
        //}
      }
    };
    l.setOpaque(true);
    l.setBackground(Color.ORANGE);
    l.setFont(l.getFont().deriveFont(style));
    l.setAlignmentX(Component.CENTER_ALIGNMENT);
    l.setAlignmentY(Component.CENTER_ALIGNMENT);
    l.setVerticalAlignment(SwingConstants.CENTER);
    l.setVerticalTextPosition(SwingConstants.CENTER);
    l.setHorizontalAlignment(SwingConstants.CENTER);
    l.setHorizontalTextPosition(SwingConstants.CENTER);
    return l;
  }
  public static JComponent makePanel() {
    JPanel p = new JPanel(new GridLayout(0, 1, 5, 5));

    JPanel p1 = new AlignmentTest4();
    p1.setBorder(BorderFactory.createTitledBorder("BoxLayout.X_AXIS"));
    p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
    p1.add(Box.createHorizontalGlue());
    p1.add(makeLabel("a", Font.PLAIN));
    p1.add(Box.createHorizontalGlue());

    JPanel p2 = new AlignmentTest4();
    p2.setBorder(BorderFactory.createTitledBorder("BoxLayout.Y_AXIS"));
    p2.setLayout(new BoxLayout(p2, BoxLayout.Y_AXIS));
    p2.add(Box.createVerticalGlue());
    p2.add(makeLabel("a", Font.PLAIN));
    p2.add(Box.createVerticalGlue());

    for (JPanel c : Arrays.asList(p1, p2)) {
      c.setBackground(Color.WHITE);
      p.add(c);
    }
    return p;
  }
  public static JComponent makeUI() {
    final JPanel p = new JPanel(new BorderLayout());
    p.add(makePanel());
    p.add(new JCheckBox(new AbstractAction("MinimumSize") {
      @Override public void actionPerformed(ActionEvent e) {
        FLAG = ((JCheckBox) e.getSource()).isSelected();
        SwingUtilities.updateComponentTreeUI(p);
      }
    }), BorderLayout.SOUTH);
    return p;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new JFrame("Alignment Test");
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(makeUI());
    f.pack();
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}
Schlock answered 6/1, 2015 at 6:41 Comment(3)
Very interesting. This works well, but it brings up another question for me: I don't understand why Java is using the getMinimumSize() method of the JPanel to figure out how to center the JLabel to begin with. Shouldn't it be using the current size of the JPanel, not the minimum size?Taille
The getMinimumSize() method of the JPanel is irrelevant in this case. The "this" keyword refers to the anonymous inner class(JLabel) itself. @Override public Dimension getMinimumSize() { System.out.println(this instanceof JLabel); /* ture */ return this.getPreferredSize(); }Schlock
Ah, okay. I guess in that case the revised question would be: why is the getMinimumSize() method of the JLabel being used? In order to determine the measurement for centering, I would imagine the current size would be used, not the minimum size.Taille
D
4

Using JDK7 on Windows 7 none of the characters are center aligned.

I made some changes to display a JTextField and I played with the columns of the JTextField (1, 3, 5). As the columns increased the center aligned improved and was reasonable at columns 5 and above. So the problem is somehow related to the width of the component.

I would guess there is some weird rounding error in the layout. This seems like a bug to me.

In case you are interested in a layout that provides some similar functionality to the BoxLayout you can check out the Relative Layout. The changes to your example are minor:

import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;

public class AlignmentTest extends JPanel
{
    public AlignmentTest(char label, int style)
    {
        JLabel l = new JLabel(Character.toString(label));
        setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
        setBackground(Color.WHITE);
//        setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
        setLayout(new RelativeLayout(RelativeLayout.Y_AXIS));
        setPreferredSize(new Dimension(300,50));
//        add(Box.createVerticalGlue());
        add(Box.createVerticalGlue(), new Float(1));
        add(l);
            l.setFont(l.getFont().deriveFont(style));
            l.setAlignmentX(CENTER_ALIGNMENT);
            l.setHorizontalAlignment(JLabel.CENTER);
//        add(Box.createVerticalGlue());
        add(Box.createVerticalGlue(), new Float(1));
    }
    public static void main(String[] args)
    {
        JFrame f = new JFrame("Alignment Test");
        JScrollPane scroller = new JScrollPane();
            JPanel panel = new JPanel(new GridLayout(1,0,5,5));
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new GridLayout(1,0,5,5));
        f.add(new AlignmentTest('a',Font.PLAIN));
        f.add(new AlignmentTest('a',Font.BOLD));
        f.add(new AlignmentTest('a',Font.ITALIC));
        f.pack();
        f.setVisible(true);
    }
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawLine(getWidth()/2,0,getWidth()/2,getHeight());
    }
}
Downfall answered 6/1, 2015 at 3:51 Comment(3)
The RelativeLayout is definitely an adequate workaround. I haven't heard of it before, but it's something I could use here & in future applications. Though, I had to go online & copy the source code for RelativeLayout to my project in order to use it. I have Java version 1.7.0_25. Do I need to update in order to have the RelativeLayout class built into Java without me needing to copy the source code manually?Taille
@ScottHetrick, RelativeLayout is just some (unsupported) code that I wrote to provide the functionality as described in the link. There is no jar file or anything for it, so yes you use it like any class you write yourself. The class is not version dependent.Downfall
Oh, I gotcha. That's impressive. Thanks for the recommendation, I'll likely be using that in the future.Taille
I
4

The effect you observe appears to be an artifact of the way BoxLayout works. Interpolating from How to Use BoxLayout: Box Layout Features, "When a BoxLayout lays out components from left to right, … Any extra space appears at the right of the container." When the enclosing container's initial size is a small multiple of the label's (fixed) size, as shown below, the anomaly is minimal; stretch the frame horizontally to see how it grows. One work-around would be to minimize the degree to which the enclosing container's preferred size is artificially enlarged.

image

import javax.swing.*;
import java.awt.*;

public class AlignmentTest extends JPanel {
    private final JLabel l;
    public AlignmentTest(String label, int style) {
        setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
        setBackground(Color.WHITE);
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        l = new JLabel(label, JLabel.CENTER);
        l.setFont(l.getFont().deriveFont(style));
        l.setAlignmentX(CENTER_ALIGNMENT);
        l.setOpaque(true);
        l.setBackground(Color.cyan);
        add(Box.createVerticalGlue());
        add(l);
        add(Box.createVerticalGlue());
    }

    @Override
    public Dimension getPreferredSize() {
        int w = l.getPreferredSize().width;
        int h = l.getPreferredSize().height;
        return new Dimension(w * 3, h * 3);
    }

    public static void main(String[] args) {
        JFrame f = new JFrame("Alignment Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new GridLayout(1, 0, 5, 5));
        f.add(new AlignmentTest("aMa", Font.PLAIN));
        f.add(new AlignmentTest("aMa", Font.BOLD));
        f.add(new AlignmentTest("aMa", Font.ITALIC));
        f.pack();
        f.setVisible(true);
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
    }
}
Interscholastic answered 6/1, 2015 at 4:48 Comment(1)
It is a viable workaround, but in order to perfectly "solve" the issue a more direct solution would be needed.Taille

© 2022 - 2024 — McMap. All rights reserved.