JComboBox 自定义单元格渲染器 - 如何获取 Combo 的 isEnabled() 状态?

JComboBox custom cell renderer -how to get isEnabled() state of Combo?

我已经为 JComboBoxes 创建了一个基于 DefaultListCellRenderer 的自定义单元格渲染器(我的渲染器基本上显示一种颜色)。 到目前为止效果很好。

唯一的问题是,当 JComboBox 被禁用时: 在这种情况下,我还应该以不那么闪亮的方式绘制颜色(以显示它处于非活动状态)。但是如何获取 ListCellRenderer 中的状态?

我尝试了 isEnabled() 或 component.isEnabled(),但这似乎无法访问/给我组合的实际状态。在 DefaultListCellRenderer 中,有一个对 list.isEnabled() 的查询,但这对我来说没有意义(它也不起作用)。

有什么想法吗?

有点棘手但可能 ;)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.util.function.Predicate;

import javax.annotation.Nullable;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

/**
 * <code>ComboTest</code>.
 */
public class ComboTest {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new ComboTest()::startUp);
    }

    private void startUp() {
        JFrame frm = new JFrame("Combo test");
        JComboBox<String> combo = new JComboBox<>(new String[] {"One", "Two", "Three"});
        combo.setRenderer(new EnablementCellRenderer());
        JButton b = new JButton("Toggle Enabled");
        b.addActionListener(l -> {
            combo.setEnabled(!combo.isEnabled());
            combo.repaint();
        });
        frm.add(combo);
        frm.add(b, BorderLayout.EAST);
        frm.pack();
        frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frm.setLocationRelativeTo(null);
        frm.setVisible(true);
    }

    private class EnablementCellRenderer extends BasicComboBoxRenderer {

        @Override
        protected void paintComponent(Graphics g) {
            JComboBox<?> combo = getFirstAncestorOfClass(this, JComboBox.class);
            setForeground(combo.isEnabled() ? Color.BLUE : Color.GREEN);
            super.paintComponent(g);
        }
    }

    /**
     * Searches for the first ancestor of the given component which is the instance of the given class.
     *
     * @param aStart start component to search. If the component is instance of the class - it will be returned.
     * @param condition condition used to determine the component.
     * @return first ancestor of the given component which is the instance of the given class. Null if no such component found.
     */
    @Nullable
    public static Container getFirstAncestor(Component aStart, Predicate<Component> condition) {
        Container result = null;
        Component base = aStart;
        while ((result == null) && (base.getParent() != null || getInvoker(base) != null)) {
            base = getInvoker(base) == null ? base.getParent() : getInvoker(base);

            result = condition.test(base) ? (Container) base : null;
        }
        return result;
    }

    /**
     * Searches for the first ancestor of the given component which is the instance of the given class.
     *
     * @param aStart start component to search. If the component is instance of the class - it will be returned.
     * @param aClass class of component.
     * @return first ancestor of the given component which is the instance of the given class. Null if no such component found.
     * @param <E> class of component.
     */
    @Nullable
    public static <E> E getFirstAncestorOfClass(Component aStart, Class<E> aClass) {
        return aClass.cast(getFirstAncestor(aStart, aClass::isInstance));
    }

    /**
     * Gets the invoker of the given component when it's a pop-up menu.
     *
     * @param c component which invoker must be found.
     * @return the invoker when the given component is a pop-up menu or null otherwise.
     */
    private static Component getInvoker(Component c) {
        return c instanceof JPopupMenu ? ((JPopupMenu) c).getInvoker() : null;
    }

}

我会采用务实的方式,将组合框传递给渲染器,例如

class ColorIcon implements Icon {
    final Color color;
    public ColorIcon(Color color) { this.color = color; }
    @Override public void paintIcon(Component c, Graphics g, int x, int y) {
        g.setColor(color);
        g.fillRect(x, y, getIconWidth(), getIconHeight());
    }
    @Override public int getIconWidth() { return 40; }
    @Override public int getIconHeight() { return 12; }
}
Color[] colors = { Color.YELLOW, Color.RED, Color.LIGHT_GRAY, Color.BLUE, Color.GREEN };
JComboBox<Color> cmb = new JComboBox<>(colors);
cmb.setRenderer(new DefaultListCellRenderer() {
    @Override
    public Component getListCellRendererComponent(
        JList<?> list, Object value, int index, boolean sel, boolean focus) {

        Color color = (Color)value;
        Component c = super.getListCellRendererComponent(list, value, index, sel, focus);
        setIcon(new ColorIcon(cmb.isEnabled()? color: color.darker()));
        setText(String.format("#%06x", color.getRGB() & 0xFFFFFF));
        return c;
    }
});
JCheckBox chk = new JCheckBox("Disabled?");
chk.addChangeListener(ev -> cmb.setEnabled(!chk.isSelected()));
JFrame f = new JFrame("Test");
f.add(cmb, BorderLayout.PAGE_START);
f.add(chk, BorderLayout.PAGE_END);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);

这意味着您不能在不同的组合框之间共享此渲染器的单个实例,但这很少成为问题。事实上,由于共享这样一个渲染器意味着在渲染过程中可能会发生使组件适应不同父级的情况,因此共享可能比实例化几个渲染器产生更高的成本。

您仍然可以使逻辑可共享,例如

private static JComboBox<Color> setupColorRender(JComboBox<Color> cmb) {
    ListCellRenderer<? super Color> def = cmb.getRenderer();
    ListCellRenderer<? super Color> r
        = def instanceof JLabel? def: new DefaultListCellRenderer();
    JLabel label = (JLabel)def;
    cmb.setRenderer((list, color, index, sel, focus) -> {
        Component c = r.getListCellRendererComponent(list, color, index, sel, focus);
        label.setIcon(new ColorIcon(cmb.isEnabled()? color: color.darker()));
        label.setText(String.format("#%06x", color.getRGB() & 0xFFFFFF));
        return c;
    });
    return cmb;
}
Color[] colors = { Color.YELLOW, Color.RED, Color.LIGHT_GRAY, Color.BLUE, Color.GREEN };
JComboBox<Color> cmb = setupColorRender(new JComboBox<>(colors));
JCheckBox chk = new JCheckBox("Disabled?");
chk.addChangeListener(ev -> cmb.setEnabled(!chk.isSelected()));
JFrame f = new JFrame("Test");
f.add(cmb, BorderLayout.PAGE_START);
f.add(chk, BorderLayout.PAGE_END);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);

可以为任意数量的组合框调用此 setupColorRender 方法。它进一步使用委托而不是典型的子类化,只要它仍然是 JLabel 的子类,它就允许使用提供的原始外观和感觉的渲染器。这种方法在某些外观和感觉上具有更好的视觉效果。