#java #swing #jcombobox #listcellrenderer
Вопрос:
Я создал пользовательский визуализатор ячеек для JComboBoxes на основе DefaultListCellRenderer (мой в основном показывает цвет). Пока что это работает хорошо.
Единственная проблема заключается в том, когда JComboBox отключен: в этом случае я также должен покрасить цвет менее блестящим способом (чтобы показать, что он неактивен). Но как мне получить статус в ListCellRenderer ?
Я попробовал IsEnabled() или component.IsEnabled(), но это кажется недоступным / не дает мне фактического состояния комбинации. В DefaultListCellRenderer есть запрос к list.IsEnabled (), но это не имеет значения для меня (и это тоже не работает).
Есть какие-нибудь идеи?
Комментарии:
1. Вы разделяете средство визуализации между различными комбинациями или что мешает вам передать комбинацию в средство визуализации?
Ответ №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("#x", color.getRGB() amp; 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("#x", color.getRGB() amp; 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
. Этот подход дает лучшие визуальные результаты для некоторых видов и ощущений.
Комментарии:
1. Спасибо, да, это выглядит менее страшно и, вероятно, как-то более практично. Однако мне нравится тот факт, что для первого решения мне не нужно ничего изменять, кроме самого класса визуализации.
Ответ №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) amp;amp; (base.getParent() != null || getInvoker(base) != null)) {
base = getInvoker(base) == null ? base.getParent() : getInvoker(base);
result = condition.test(base) ? (Container) base : null;
}
return resu<
}
/**
* 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;
}
}
Комментарии:
1. Спасибо — выглядит немного пугающе, но работает как заклинание!