Java BoxLayout 中单个字符在 Y 轴上的对齐不居中

6

在Java中,有些字符在沿y轴居中的BoxLayout中对齐似乎存在问题。我不知道是什么原因,我已经创建了一个SSCCE来演示这种效果。在这个例子中,我只使用字符'a',并在每个JPanel的正中间画一条线,以演示每种情况与中心的偏差有多大。加粗文本的情况似乎对齐得很好,但普通格式和斜体都严重偏离中心,尽管使用了setAlignmentX和setHorizontalAlignment。希望能帮助理解这种效果。

如果问题出现在我的电脑上的Java上,这是运行SSCCE时在我的屏幕上显示的图像,其中加载了沿y轴具有BoxLayout的三个不同的JPanels,并在每个JPanel中放置一个仅包含字符'a'的居中JLabel:

enter image description here

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

1
为了清晰起见,请使用 style names - trashgod
好的,我没有考虑到那一点。现在我已经改变了代码,使用style名称。感谢您的建议。 - Scott Hetrick
3个回答

5

如果要避免“Box Layout Features: … Any extra space appears at the right of the container”,您需要重写JLabel#getMinimumSize()方法,返回与JLabel#getPreferredSize()相同的Dimension

抱歉,我误解了。

如@camickr已经说过的那样,

我猜布局中存在一些奇怪的舍入误差。这对我来说似乎是一个 bug。

非常正确。

修复后的示例:

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

非常有趣。这个方法很有效,但是它引发了另一个问题:我不明白为什么Java要使用JPanelgetMinimumSize()方法来确定如何将JLabel居中。难道它不应该使用JPanel的当前大小而不是最小大小吗? - Scott Hetrick
在这种情况下,JPanelgetMinimumSize() 方法是无关紧要的。 “this” 关键字指的是匿名内部类(JLabel)本身。 @Override public Dimension getMinimumSize() { System.out.println(this instanceof JLabel); /* ture */ return this.getPreferredSize(); } - aterai
啊,好的。我猜在这种情况下,修改后的问题应该是:为什么要使用JLabelgetMinimumSize()方法?为了确定居中的测量值,我想应该使用当前大小而不是最小大小。 - Scott Hetrick

4
在Windows 7上使用JDK7时,没有任何字符居中对齐。
我做了一些更改来显示一个JTextField,并且我调整了JTextField的列数(1、3、5)。随着列数的增加,居中对齐得到了改善,在列数达到5及以上时是合理的。因此,问题与组件的宽度有关。
我猜测布局中存在一些奇怪的舍入误差。对我来说,这似乎是一个错误。
如果您对提供类似BoxLayout功能的布局感兴趣,可以查看Relative Layout。对您的示例进行的更改很小:
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());
    }
}

RelativeLayout 绝对是一个足够的解决方法。我以前没有听说过它,但这是我现在和将来应用中可以使用的东西。不过,我必须在线复制 RelativeLayout 的源代码到我的项目中才能使用它。我使用的是 Java 版本 1.7.0_25。我需要更新版本才能使 RelativeLayout 类内置于 Java 中,而无需手动复制源代码吗? - Scott Hetrick
@ScottHetrick,RelativeLayout只是我编写的一些(不受支持的)代码,旨在提供链接中描述的功能。没有任何jar文件或其他东西,因此您可以像编写自己的任何类一样使用它。该类不依赖于版本。 - camickr
哦,我明白了。这很不错。谢谢您的推荐,我将来可能会使用它。 - Scott Hetrick

4
您观察到的效果似乎是BoxLayout的工作方式造成的人为影响。从如何使用BoxLayout:BoxLayout功能中推断,“当BoxLayout从左到右布置组件时,……任何额外的空间都出现在容器的右侧。” 当封闭容器的初始大小是标签(固定)大小的小倍数时,如下所示,异常情况很小;将框架水平拉伸以查看其增长方式。一个解决方法是最小化人为增大封闭容器的首选大小的程度。
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());
    }
}

这是一个可行的解决方法,但为了完美地“解决”问题,需要更直接的解决方案。 - Scott Hetrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接