views:

67

answers:

4

Is there a look-and-feel-independent way to align a component (e.g. a JLabel) horizontally with the text of a JCheckBox?

I am trying to use values from the UIDefaults to predict the location of the text relative to the top-left corner of the JCheckBox. I have found a combination that gives the right result for the Metal, Windows, Motif and Aqua Look-and-Feels:
Example: Metal (correctly-aligned)

But not in Nimbus:
Example: Nimbus (incorrectly-aligned)

Is there a utility method somewhere that will reliably give X,Y offsets for the text in all Look-and-Feels?


Code (note: to avoid any layout side-effects I used a null layout for this test):

import java.awt.Insets;

import javax.swing.JApplet;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.border.Border;

public class AlignCheckBoxText extends JApplet {

    public AlignCheckBoxText() {
        setLayout(null);
        checkBox = new JCheckBox("Hello, World!");
        label = new JLabel("Hello, World!");
        add(checkBox);
        add(label);
    }

    @Override
    protected void validateTree() {
        checkBox.setLocation(0, 0);
        checkBox.setSize(checkBox.getPreferredSize());
        int labelX = UIManager.getIcon("CheckBox.icon").getIconWidth();
        Insets cbInsets = UIManager.getInsets("CheckBox.margin");
        if (cbInsets != null) labelX += cbInsets.left + cbInsets.right;
        Border cbBorder = UIManager.getBorder("CheckBox.border");
        if (cbBorder != null) {
            Insets borderInsets = cbBorder.getBorderInsets(checkBox);
            if (borderInsets != null) {
                labelX += borderInsets.left; 
            }
        }
        label.setLocation(labelX, checkBox.getHeight());
        label.setSize(label.getPreferredSize());
        super.validateTree();
    }

    private JCheckBox checkBox;
    private JLabel label;

}
+1  A: 

This is not a complete response, but it might help you :

If you add checkBox.getIconTextGap() to the value of labelX, the alignment seems to be OK with Nimbus, but not OK with metal or GTK.

barjak
finnw
This was the solution I was going to suggest as well, although you should note that LAF's are not forced to use any of the UIManager default values. Also I do get a non zero value for the default LAF's. Check out the UIManager Defaults program (http://tips4java.wordpress.com/2008/10/09/uimanager-defaults/). All the examples I've ever seen in the Swing tutorial override the init() method to create the GUI. Maybe the problem is with the method you are overriding and the LAF isn't installed yet? Try the normal way or try creating a JFrame for you demo to see if there is a difference.
camickr
+1  A: 

If you use GroupLayout, the Netbeans GUI builder can align the text of a check box and a label on the next line.... sort of.

It appears to work well for Nimbus, but in the other LAF the label text is one pixel too far to the right. Looking at the generated code for the GroupLayout in the GUI builder, it makes a gap of 22x22 in front of the label. For Nimbus, 22 seems right, but for the other LAFs it appears to be 21.

The generated code looks like this.

GroupLayout layout = new GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHortizontalGroup(
  layout.createParallelGroup(Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
      .addContainerGap()
      .addGroup(layout.createParallelGroup(Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
          .addGap(22,22,22)
          .addComponent(label)
        )
        .addComponent(checkBox)
      )
    )
);

layout.setVerticalGroup(
  layout.createParallelGroup(Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
      .addContainerGap()
      .addComponent(checkBox)
      .addPreferredGap(ComponentPlacement.RELATED)
      .addComponent(label)
    )
);

This code would go in the constructor of the sample class after the assignments of label and checkBox. Also, remove the setLayout(null);

All of this being said, based on what I've seen using the GUI builder, I'd recommend the following for real:

  1. remove the Border cbBorder bit from the sample
  2. add the checkBox.getIconTextGap() to the labelX.
  3. If the LAF is nimbus, then add 4 to labelX. :)
Jay R.
+2  A: 

You can right-align the label and set its width to be checkbox's width minus right border. It works for all LAFs with Windows JDK1.6.0_21, including Nimbus. I don't have a Mac so can't test Aqua.

Here's your code very slightly modified:

class AlignCheckBoxText extends JApplet {

    public AlignCheckBoxText() {
        setLayout(null);
        checkBox = new JCheckBox("Hello, World!");
        label = new JLabel("Hello, World!");
        add(checkBox);
        add(label);        
        label.setHorizontalAlignment(JLabel.TRAILING);
    }

    @Override
    protected void validateTree() {
        checkBox.setLocation(0, 0);
        checkBox.setSize(checkBox.getPreferredSize());
        int x = 0;
        Border cbBorder = UIManager.getBorder("CheckBox.border");
        if (cbBorder != null) {
            Insets borderInsets = cbBorder.getBorderInsets(checkBox);
            if (borderInsets != null) {
                x += borderInsets.right;
            }
        }
        label.setLocation(0, checkBox.getHeight());
        label.setSize(new Dimension(checkBox.getPreferredSize().width - x, label.getPreferredSize().height));
        super.validateTree();
    }

    private JCheckBox checkBox;
    private JLabel label;

}
Geoffrey Zheng
I wish I could delete or down vote my answer. It's really brain dead (no offense to OP, it's all my fault). It only works when checkbox and label have identical text. You could hack it by setting the real checkbox text after setting label size, but then you'd have to do that whenever label text changes. Also it fails for right-to-left locale. A real answer would probably need to delve into check box UI, which I'm not familiar with. Sorry.
Geoffrey Zheng
Even though this answer has drawbacks, I think this should stay. Don't delete it. Although maybe the OP could unaccept it.
Jay R.
+3  A: 

(To OP: please unaccept my earlier wrong answer, and I'll delete it. Sorry!)

You can use a checkbox that doesn't paint the checkbox for your labels. This works for all LAFs in Windows JDK6, including Nimbus. I don't have a mac so can't test Aqua.

class CheckBoxLabel extends JCheckBox
{
    public CheckBoxLabel(String string)
    {
        super(string);
    }

    public void updateUI()
    {
        setUI(new CheckBoxLabelUI());
    }
}

class CheckBoxLabelUI extends BasicCheckBoxUI
{
    public void installUI(JComponent c)
    {
        super.installUI(c);
        Icon i = super.getDefaultIcon();
        icon_ = new EmptyIcon(i.getIconWidth(), i.getIconHeight());
    }

    public Icon getDefaultIcon()
    {
        return icon_;
    }

    private Icon icon_;
}

class EmptyIcon implements Icon
{
    public EmptyIcon()
    {
        this(0, 0);
    }

    public EmptyIcon(int width, int height)
    {
        width_ = width;
        height_ = height;
    }

    public int getIconHeight()
    {
        return height_;
    }

    public int getIconWidth()
    {
        return width_;
    }

    public void paintIcon(Component c, Graphics g, int x, int y)
    {
    }

    private int width_;
    private int height_;
}
Geoffrey Zheng