How can I replicate disabled appearance for a JCheckBox?
Take the following example code. There is some bug in Swing which doesn't render disabled components as disabled if the component contains HTML. Aside from reporting the issue, which I hope a colleague has already taken care of, is there some good way to work around the problem?
Whatever solution I take, I want it to be a global fix as opposed to something that needs to be hacked into every check box in the application.
I tried making a custom UI for the check box which calls setForeground
before and after the painting, but it turns out that by calling setForeground
, it fires an event which ultimately results in it calling repaint()
, which calls the renderer, ...
import java.awt.GridLayout;
import java.util.Arrays;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import 开发者_如何学Cjavax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class TestCheckBoxes extends JFrame
{
public TestCheckBoxes()
{
JCheckBox checkBox1 = new JCheckBox("Enabled, plain text");
JCheckBox checkBox2 = new JCheckBox("<html><p>Enabled, HTML");
JCheckBox checkBox3 = new JCheckBox("Disabled, plain text");
checkBox3.setEnabled(false);
JCheckBox checkBox4 = new JCheckBox("<html><p>Disabled, HTML");
checkBox4.setEnabled(false);
setLayout(new GridLayout(4, 1));
for (JCheckBox checkBox : Arrays.asList(checkBox1, checkBox2, checkBox3, checkBox4))
{
checkBox.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
add(checkBox);
}
((JComponent) getContentPane()).setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
pack();
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
TestCheckBoxes frame = new TestCheckBoxes();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
You could separate the checkbox and label into their own components and simply make a checkbox without a label. You could also maybe add them to a panel of their own and override the setEnabled()
method of the panel to simply enable/disable the checkbox and change the label's color. Take this code snippet for example:
final JCheckBox checkbox = new JCheckBox();
final JLabel label = new JLabel();
JPanel panel = new JPanel() {
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
checkbox.setEnabled(enabled);
if (enabled)
label.setForeground(Color.BLACK);
else
label.setForeground(Color.GRAY);
}
};
panel.add(checkbox);
panel.add(label);
Note that checkbox
and label must be final to use them in our panel's setEnabled()` method. Depending on how often you're inserting HTML into your checkboxes, you can always create your own component class to do this as well.
public class HTMLCheckBox extends JPanel {
private JCheckBox checkbox = new JCheckBox();
private JLabel label = new JLabel();
private Color disabledColor = Color.GRAY;
private Color enabledColor = Color.BLACK;
public HTMLCheckBox(String text) {
label.setText(text);
add(checkbox);
add(label);
}
public boolean isSelected() {
return checkbox.isSelected();
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
checkbox.setEnabled(enabled);
if (enabled)
label.setForeground(enabledColor);
else
label.setForeground(disabledColor);
}
}
And then add your own constructors and methods as you so desire. For example, override setBackground()
to have it set the background for the panel, checkbox, and label. A setText()
method to change the label text would probably also be convenient. Whatever you'd want it to do. And maybe even setters for enabledColor
and disabledColor
to allow you to change these at will.
I would suggest to start using renderers instead of that. You can use HTML inside the renderer (such as JLabel) and your problem will go away by itself :)
More information is at http://java.sun.com/docs/books/tutorial/uiswing/components/combobox.html#renderer
I gotta stop answering my own questions... must be something to do with time zones and getting time to think about it during sleep.
Somewhere else in the application...
UIManager.put("CheckBoxUI", "package.for.CustomisedWindowsCheckBoxUI");
And then this is the implementation, but this is still pretty hacky and it uses a utility method to generate the HTML colour string which is less than great for posting here.
Note that this only fixes it for Windows L&F. Metal L&F has also been witnessed to have a problem but the solution is the same, just subclass BasicCheckBoxUI instead.
import java.awt.Graphics;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import com.sun.java.swing.plaf.windows.WindowsCheckBoxUI;
import com.blah.util.ColourUtils;
/**
* Customisation of Windows check box UI to fix bugs.
*/
public class CustomisedWindowsCheckBoxUI extends WindowsCheckBoxUI {
/**
* Factory method called from Swing.
*
* @param b the check box.
* @return the UI.
*/
public static ComponentUI createUI(JComponent b) {
// TODO: Sun have an AppContext they use to store these once per app.
// Might be more sociable to use something like that.
return new CustomisedWindowsCheckBoxUI();
}
@Override
public void paint(Graphics g, JComponent c) {
AbstractButton b = (AbstractButton) c;
// Works around a bug in BasicButtonUI where a disabled button with HTML markup in the text will
// not appear to be disabled.
// TODO: Find a way to fix this globally for HTML rendering. It seems odd that it isn't working.
// I can see the code in BasicHTML.createHTMLView which uses the foreground colour, which is
// obviously why setForeground() works as a workaround.
if (b.getForeground() instanceof ColorUIResource) {
View view = (View) c.getClientProperty(BasicHTML.propertyKey);
if (view != null) {
// Ensure that we don't update the renderer if the value hasn't changed.
String cachedHtmlFor = (String) c.getClientProperty("cachedHtmlFor");
String key = String.format("%s:%s", c.isEnabled(), b.getText());
if (!key.equals(cachedHtmlFor)) {
c.putClientProperty("cachedHtmlFor", key);
if (c.isEnabled()) {
BasicHTML.updateRenderer(c, b.getText());
} else {
BasicHTML.updateRenderer(c, String.format("<html><div style='color: %s'>%s",
ColourUtils.toHtmlColour(b.getBackground().darker()),
b.getText()));
}
}
}
}
super.paint(g, c);
}
}
精彩评论