【问题标题】:JComboBox custom cell renderer -how to get isEnabled() state of Combo?JComboBox 自定义单元格渲染器 - 如何获取组合的 isEnabled() 状态?
【发布时间】:2021-11-14 05:22:56
【问题描述】:

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

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

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

有什么想法吗?

【问题讨论】:

  • 您是否在不同的组合框之间共享渲染器,或者是什么阻止您将组合框传递给渲染器?

标签: java swing jcombobox listcellrenderer


【解决方案1】:

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

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 的子类,它就允许使用提供的原始外观渲染器。这种方法在某些外观和感觉上具有更好的视觉效果。

【讨论】:

  • 谢谢,是的,这看起来不那么可怕,而且可能更实用。但是,我喜欢第一个解决方案,除了渲染器类本身之外,我不需要修改任何其他内容。
【解决方案2】:

有点棘手但可能;)

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

}

【讨论】:

  • 谢谢 - 看起来有点吓人,但效果很好!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-13
  • 2012-08-05
  • 2015-01-01
  • 2018-12-26
  • 1970-01-01
相关资源
最近更新 更多