Как изменить цвет подсветки сфокусированного JComboBox

Позвольте мне сначала объяснить, чего я хочу достичь. Я создаю форму ввода данных в Swing, состоящую из нескольких JComboBoxes & JTextFields. Процедура проверки выполняет итерации по этим компонентам и определяет, являются ли значения, указанные для каждого элемента управления «действительными» (подробности проверки неактуальны для целей данного примера).

Когда в подпрограмме указано, что компонент содержит недопустимое значение, я хочу изменить цвет фона этого поля, а также цвет переднего плана / текста этого поля – чтобы пользователь дал понять, что есть проблема с этим полем.

Если поле считается «действительным», я хочу, чтобы фон элемента управления был белым, а передний план / текст – черным.

До сих пор все довольно просто, и все они достижимы в прилагаемом демо-коде ниже.

Когда Combo Box содержит допустимое значение и сфокусирован – фон редактора внутри комбо настроен на синеватый цвет, которым я совершенно доволен.

Тем не менее, я пытаюсь изменить цвет, который используется для выделения сфокусированного поля со списком, когда этот поле со списком содержит недопустимое значение. Несмотря на то, что цвет фона в поле со списком был розовым, если элемент управления сфокусирован, он по-прежнему использует синий цвет, чтобы указать, что он сфокусирован.

Пример недействительного поля, которое сфокусировано: http://postimg.org/image/ne9xgjch3/

Хотя я понимаю, что это совершенно нормальное поведение, то, что я хотел бы сделать, – это изменить цвет, который используется для выделения одного из «недопустимых» полей в более темный оттенок цвета, который не был сфокусированным и недействительным. – так что пользователь все еще может видеть, какой элемент управления сконцентрирован, и он все еще остается розовым. Я ценю, что это может показаться мелочным, но мой конечный пользователь настаивает на том, что при фокусировке все поле остается розовым (точнее, другим оттенком розового). Это то, что моя эутопия, сфокусированное и «недействительное» поле, будет выглядеть так:

http://postimg.org/image/9793bqcfj/

Я попытался расширить classы DefaultListCellRenderer & BasicComboBoxEditor и установить их в поле со списком как рендеринг и редактор соответственно. У меня создалось впечатление, что Редактор будет тем, где мне нужно сосредоточить свое внимание, поэтому в методе getEditorComponent этого classа я бы вернул метку с соответствующим фоном и передним планом – однако из этого метода у меня нет способа зная, имеет ли контроль фокус, поэтому не имеет способа определить, как я должен отформатировать возвращенную метку. Кроме того, как только я начал устанавливать Редактор против combobox, я, казалось, потерял способность сосредоточить контроль вообще, хотя это, возможно, было моим недостатком знаний о том, как реализовать редактор.

Я тоже читал об BasicComboBoxUI, но ничто из того, что я встречал, не выделялось как решение.

Пожалуйста, кто-то любезно указал мне в правильном направлении, я провел дни, занимаясь этим, и это действительно начинает меня пугать. Пожалуйста, извините демо-код, созданный netbeans, это просто позволило мне быстро сбить демо.

package com.test; import java.awt.*; public class TestForm extends javax.swing.JFrame { public TestForm() { initComponents(); } @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { cboOne = new javax.swing.JComboBox(); txtOne = new javax.swing.JTextField(); txtTwo = new javax.swing.JTextField(); btnValidate = new javax.swing.JButton(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); cboOne.setBackground(new java.awt.Color(255, 255, 255)); cboOne.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Valid Value", "Invalid Value", "Another Invalid Value" })); txtOne.setText("123"); txtTwo.setText("123"); btnValidate.setText("Validate"); btnValidate.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnValidateActionPerformed(evt); } }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(cboOne, 0, 376, Short.MAX_VALUE) .addComponent(txtOne) .addComponent(txtTwo) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) .addComponent(btnValidate))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(cboOne, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(txtOne, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(txtTwo, javax.swing.GroupLayout.PREFERRED_SIZE, 58, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(btnValidate) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); pack(); }// //GEN-END:initComponents private void btnValidateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnValidateActionPerformed //Check if the selection in the ComboBox is valid... if (((String)cboOne.getSelectedItem()).equals("Valid Value")) { //Selected Value is Valid. //We want the combo box to appear with a white background //and black text. cboOne.setBackground(Color.white); cboOne.setForeground(Color.black); } else { //The value specified is invalid. //We want to highlight the field in pink to identify an issue, //and change the color of the text to red too: cboOne.setBackground(Color.pink); cboOne.setForeground(Color.red); } //Check if the value entered into the first Text Field is valid... if (txtOne.getText().equals("123")) { //Selected Value is Valid. //We want the text box to appear with a white background //and black text. txtOne.setBackground(Color.white); txtOne.setForeground(Color.black); } else { //Selected Value is invalid. //We want the text box to appear with a pink background //and red text. txtOne.setBackground(Color.pink); txtOne.setForeground(Color.red); } //Check if the value entered into the second Text Field is valid... if (txtTwo.getText().equals("123")) { //Selected Value is Valid. //We want the text box to appear with a white background //and black text. txtTwo.setBackground(Color.white); txtTwo.setForeground(Color.black); } else { //Selected Value is invalid. //We want the text box to appear with a pink background //and red text. txtTwo.setBackground(Color.pink); txtTwo.setForeground(Color.red); } }//GEN-LAST:event_btnValidateActionPerformed public static void main(String args[]) { /* Create and display the form */ java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new TestForm().setVisible(true); } }); } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton btnValidate; private javax.swing.JComboBox cboOne; private javax.swing.JComboBox jComboBox1; private javax.swing.JComboBox jComboBox2; private javax.swing.JTextField txtOne; private javax.swing.JTextField txtTwo; // End of variables declaration//GEN-END:variables } 

Обновить

Забудьте упомянуть. Причина, по которой у вас возникли проблемы с раскраской вашего combobox, заключается в том, что цвет, который вы видите, является цветом выделения. Цвет определяется в настройках внешнего вида и ощущения, нет никакого способа изменить эти цвета для одного компонента, не набирая свой собственный внешний вид делегата, который я лично не сделал бы

Это пример предоставления подсветки недопустимым полям с использованием JXLayer (теперь JLayer , но у меня не было времени его преобразовать), в то время как в этом примере используется API InputVerifer , нет причин, по которым это нужно, оно просто используется для часть примера. Было бы довольно легко сделать подсветку после проверки, основное внимание будет уделено функциональности выделения, а не методу валидации;).

Это основано на идее Кирилла Гроучникова в его блоге Pushing Pixels, наложениях валидации с использованием JXLayer

Это прототип идеи, которую я сделал некоторое время назад, код все еще нуждается в некоторой настройке для повышения производительности, но в остальном вполне функциональный … Я бы предпочел лучше встроенную поддержку для проверки в реальном времени … но это только я 😉

основной момент

Основной тестовый class …

 import com.jhlabs.image.GaussianFilter; import java.awt.AlphaComposite; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import javax.swing.InputVerifier; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import org.jdesktop.jxlayer.JXLayer; import org.jdesktop.jxlayer.plaf.AbstractLayerUI; public class FormValidationExample { public static void main(String[] args) { new FormValidationExample(); } public FormValidationExample() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(new TestPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TestPane extends JPanel { private JXLayer layer; private javax.swing.JComboBox cboOne; private javax.swing.JTextField txtOne; private javax.swing.JTextField txtTwo; private DefaultValidationHighlightModel validationModel; private boolean ignoreValidationRequest; public TestPane() { setLayout(new BorderLayout()); JPanel content = new JPanel(new GridBagLayout()); ValidationUI ui = new ValidationUI(); validationModel = new DefaultValidationHighlightModel(ui); layer = new JXLayer<>(content, ui); add(layer); cboOne = new javax.swing.JComboBox(); cboOne.setInputVerifier(new AbstractValidationInputVerifier(validationModel) { @Override public boolean verify(JComponent input) { boolean valid = false; JComboBox cb = (JComboBox) input; String textOfOne = txtOne.getText(); String textOfTwo = txtTwo.getText(); if (cb.getSelectedIndex() == 2) { valid = true; } else if (cb.getSelectedIndex() == 0 && "123".equals(textOfOne) && "456".equals(textOfTwo)) { valid = true; } else if (cb.getSelectedIndex() == 1 && "456".equals(textOfOne) && "789".equals(textOfTwo)) { valid = true; } return valid; } }); txtOne = new javax.swing.JTextField("123", 10); txtOne.setInputVerifier(new AbstractValidationInputVerifier(validationModel) { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String text = field.getText(); return "123".equals(text) || "456".equals(text); } }); txtTwo = new javax.swing.JTextField("123", 10); txtTwo.setInputVerifier(new AbstractValidationInputVerifier(validationModel) { @Override public boolean verify(JComponent input) { JTextField field = (JTextField) input; String text = field.getText(); return "456".equals(text) || "789".equals(text); } }); cboOne.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"Only works with 123, 456", "Only works with 456, 789", "Works with everybody"})); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(4, 4, 4, 4); content.add(cboOne, gbc); content.add(txtOne, gbc); content.add(txtTwo, gbc); validateFields(); } protected void validateFields() { if (!ignoreValidationRequest) { ignoreValidationRequest = true; try { cboOne.getInputVerifier().shouldYieldFocus(cboOne); txtOne.getInputVerifier().shouldYieldFocus(txtOne); txtTwo.getInputVerifier().shouldYieldFocus(txtTwo); } finally { ignoreValidationRequest = false; } } } public abstract class AbstractValidationInputVerifier extends InputVerifier { private IValidationHighlightModel model; public AbstractValidationInputVerifier(IValidationHighlightModel model) { this.model = model; } public IValidationHighlightModel getModel() { return model; } @Override public boolean shouldYieldFocus(JComponent input) { if (verify(input)) { getModel().removeInvalidField(input); } else { getModel().addInvalidField(input); } validateFields(); return true; } } } } 

JXLayer , выделить слои пользовательского интерфейса …

 public class ValidationUI extends HighlightComponentUI { public ValidationUI() { super(Color.RED); } } public class HighlightComponentUI extends AbstractLayerUI { private List> lstHighlights; private Color highlightColor; public HighlightComponentUI(Color highlight) { highlightColor = highlight; lstHighlights = new ArrayList>(25); } protected void cleanReferences() { if (lstHighlights.size() > 0) { List> removed = new ArrayList>(lstHighlights.size()); for (WeakReference wr : lstHighlights) { Component weak = wr.get(); if (weak == null) { removed.add(wr); } } lstHighlights.removeAll(removed); setDirty(true); } } protected boolean contains(Component comp) { boolean contains = false; cleanReferences(); for (WeakReference wr : lstHighlights) { Component weak = wr.get(); if (weak.equals(comp)) { contains = true; break; } } return contains; } protected void clearHighlights() { lstHighlights.clear(); setDirty(true); } protected void addHighlight(Component comp) { if (comp != null) { if (!contains(comp)) { lstHighlights.add(new WeakReference(comp)); setDirty(true); } } } public Component[] getHighlightedComponents() { List comps = new ArrayList<>(lstHighlights.size()); for (WeakReference wr : lstHighlights) { Component comp = wr.get(); if (comp != null) { comps.add(comp); } } return comps.toArray(new Component[comps.size()]); } protected void removeHighlight(Component comp) { cleanReferences(); WeakReference toRemove = null; for (WeakReference wr : lstHighlights) { Component weak = wr.get(); if (weak.equals(comp)) { toRemove = wr; break; } } if (toRemove != null) { lstHighlights.remove(toRemove); setDirty(true); } } public Color getHighlight() { return highlightColor; } /** * Does a recursive search of all the child components of the supplied * parent looking for the supplied child * * @param parent * @param child * @return true if the child resides within the parent's hierarchy, * otherwise false */ public boolean contains(Container parent, Component child) { boolean contains = false; if (child.getParent() != null) { if (child.getParent().equals(parent)) { contains = true; } else { for (Component comp : parent.getComponents()) { if (comp instanceof Container) { if (contains((Container) comp, child)) { contains = true; break; } } } } } return contains; } @Override protected void paintLayer(Graphics2D g2, JXLayer l) { super.paintLayer(g2, l); Graphics2D c = (Graphics2D) g2.create(); JComponent view = l.getView(); while (view instanceof JXLayer) { view = (JComponent) ((JXLayer) view).getView(); } for (WeakReference wr : lstHighlights) { Component comp = wr.get(); if (comp != null && contains(view, comp)) { // A cache here would be VERY useful, would need to be mainatined // against the component instance as well as the component // size properties... BufferedImage img = new BufferedImage(comp.getWidth(), comp.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); g2d.setComposite(AlphaComposite.Clear); g2d.fillRect(0, 0, img.getWidth(), img.getHeight()); g2d.setComposite(AlphaComposite.SrcOver); comp.printAll(g2d); g2d.dispose(); BufferedImage glow = GlowEffectFactory.generateGlow(img, 8, getHighlight(), 0.75f); Point point = comp.getLocation(); point = SwingUtilities.convertPoint(comp.getParent(), point, view); int x = point.x - ((glow.getWidth() - comp.getWidth()) / 2); int y = point.y - ((glow.getHeight() - comp.getHeight()) / 2); c.drawImage(glow, x, y, l); } } c.dispose(); } } 

Класс, связанный с моделью валидации (мне нравится использовать interfaces и abstract реализации, чтобы обеспечить гибкость API и уменьшить связь, где я могу). Мой оригинальный прототип имел слой и модель пользовательского интерфейса, разделенные и обновленные с помощью поддержки ChangeListener , но я объединил их здесь для простоты …

 public class DefaultValidationHighlightModel extends AbstractValidationHighlightModel { private HighlightComponentUI ui; public DefaultValidationHighlightModel(HighlightComponentUI ui) { this.ui = ui; } @Override public void addInvalidField(Component comp) { if (!ui.contains(comp)) { ui.addHighlight(comp); fireStateChanged(); } } @Override public void removeInvalidField(Component comp) { if (ui.contains(comp)) { ui.removeHighlight(comp); fireStateChanged(); } } @Override public Component[] getInvalidFields() { return ui.getHighlightedComponents(); } } public abstract class AbstractValidationHighlightModel implements IValidationHighlightModel { private EventListenerList listenerList; public EventListenerList getListenerList() { if (listenerList == null) { listenerList = new EventListenerList(); } return listenerList; } @Override public void addChangeListener(ChangeListener listener) { getListenerList().add(ChangeListener.class, listener); } @Override public void removeChangeListener(ChangeListener listener) { getListenerList().remove(ChangeListener.class, listener); } protected ChangeListener[] getChangeListeners() { return getListenerList().getListeners(ChangeListener.class); } protected void fireStateChanged() { ChangeListener[] listeners = getChangeListeners(); if (listeners != null && listeners.length > 0) { ChangeEvent evt = new ChangeEvent(this); for (ChangeListener listener : listeners) { listener.stateChanged(evt); } } } } public interface IValidationHighlightModel { public void addInvalidField(Component comp); public void removeInvalidField(Component comp); public Component[] getInvalidFields(); public void addChangeListener(ChangeListener listener); public void removeChangeListener(ChangeListener listener); } public static class GlowEffectFactory { public static BufferedImage createCompatibleImage(int width, int height) { return createCompatibleImage(width, height, Transparency.TRANSLUCENT); } public static BufferedImage createCompatibleImage(Dimension size) { return createCompatibleImage(size.width, size.height); } public static BufferedImage createCompatibleImage(int width, int height, int transparency) { GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); BufferedImage image = gc.createCompatibleImage(width, height, transparency); image.coerceData(true); return image; } public static BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage, int method) { BufferedImage maskedImage = null; if (sourceImage != null) { int width = maskImage.getWidth(null); int height = maskImage.getHeight(null); maskedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D mg = maskedImage.createGraphics(); int x = (width - sourceImage.getWidth(null)) / 2; int y = (height - sourceImage.getHeight(null)) / 2; mg.drawImage(sourceImage, x, y, null); mg.setComposite(AlphaComposite.getInstance(method)); mg.drawImage(maskImage, 0, 0, null); mg.dispose(); } return maskedImage; } public static BufferedImage generateBlur(BufferedImage imgSource, int size, Color color, float alpha) { GaussianFilter filter = new GaussianFilter(size); int imgWidth = imgSource.getWidth(); int imgHeight = imgSource.getHeight(); BufferedImage imgBlur = createCompatibleImage(imgWidth, imgHeight); Graphics2D g2 = imgBlur.createGraphics(); g2.drawImage(imgSource, 0, 0, null); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha)); g2.setColor(color); g2.fillRect(0, 0, imgSource.getWidth(), imgSource.getHeight()); g2.dispose(); imgBlur = filter.filter(imgBlur, null); return imgBlur; } public static BufferedImage generateBlur(BufferedImage imgSource, int size) { GaussianFilter filter = new GaussianFilter(size); int imgWidth = imgSource.getWidth(); int imgHeight = imgSource.getHeight(); BufferedImage imgBlur = createCompatibleImage(imgWidth, imgHeight); Graphics2D g2 = imgBlur.createGraphics(); g2.drawImage(imgSource, 0, 0, null); g2.dispose(); imgBlur = filter.filter(imgBlur, null); return imgBlur; } public static BufferedImage generateGlow(BufferedImage imgSource, int size, Color color, float alpha) { int imgWidth = imgSource.getWidth() + (size * 2); int imgHeight = imgSource.getHeight() + (size * 2); BufferedImage imgMask = createCompatibleImage(imgWidth, imgHeight); Graphics2D g2 = imgMask.createGraphics(); int x = Math.round((imgWidth - imgSource.getWidth()) / 2f); int y = Math.round((imgHeight - imgSource.getHeight()) / 2f); g2.drawImage(imgSource, x, y, null); g2.dispose(); // ---- Blur here --- BufferedImage imgGlow = generateBlur(imgMask, size, color, alpha); // ---- Blur here ---- imgGlow = applyMask(imgGlow, imgMask, AlphaComposite.DST_OUT); return imgGlow; } } 

Предостережения

Для этого требуется JXLayer (я использовал версию 3) (которая больше не доступна в сети …) и SwingX (я использовал версию 1.6.4)

Я поместил весь исходный код JXLayer (версия 3) и примеры Piet в один ZIP- код, и я бы предложил, если вам интересно, вы возьмете копию и сохраните ее там, где это безопасно.

Вам также понадобятся фильтры JHLabs

  • Получение состояния JToggleButton
  • Перетаскиваемые прямоугольники в Java 2D
  • Рисование линии между двумя точками геометрии в JMapViewer
  • Редактор графического редактора Netbeans, создающий собственный непонятный код
  • Доступ к компонентам GUI из другого classа
  • JTable не отображается на JFrame (Java)
  • Java - прокрутите до определенного текста внутри JTextArea
  • Java Swing Timer и Animation: как собрать его вместе
  • JComponents, исчезающие после вызова mouseClicked ()
  • Как заполнить данные в JTable с помощью базы данных?
  • Java Swing: отображение изображений изнутри Jar
  • Давайте будем гением компьютера.