如何更改焦点 JComboBox 的突出显示颜色
Posted
技术标签:
【中文标题】如何更改焦点 JComboBox 的突出显示颜色【英文标题】:How can I change the highlight color of a focused JComboBox 【发布时间】:2014-10-06 03:04:54 【问题描述】:首先让我解释一下我想要实现的目标。我正在 Swing 中创建一个数据输入表单,由许多 JComboBoxes 和 JTextFields 组成。验证例程迭代这些组件并确定为每个控件指定的值是否“有效”(验证的详细信息与本示例的目的无关)。
当例程识别出组件包含无效值时,我想更改 该字段的背景颜色,以及该字段的前景色/文本颜色 - 让用户清楚地知道该字段存在问题。
如果某个字段被视为“有效”,我想将控件的背景设置为白色 - 将前景/文本设置为黑色。
到目前为止,一切都非常简单,并且都可以在下面附加的演示代码中实现。
当 Combo Box 包含有效值并获得焦点时 - 组合内的编辑器背景设置为蓝色,我对此非常满意。
但是,我想要实现的是在组合框包含无效值时更改用于突出显示焦点组合框的颜色。尽管已将组合框的背景颜色更改为粉红色,但如果控件已获得焦点,它仍然使用蓝色来指示它已获得焦点。
聚焦的无效字段示例: http://postimg.org/image/ne9xgjch3/
虽然我明白这是完全正常的行为,但我想做的是将用于突出显示“无效”字段之一的颜色更改为较暗的颜色阴影,即非聚焦和无效的control 会有 - 这样用户仍然可以看到哪个控件被聚焦,并且它仍然是粉红色的。我很欣赏这可能看起来很琐碎,但我的最终用户坚持在聚焦时整个字段保持粉红色(或者更确切地说,是不同的粉红色)。这就是我的乌托邦,一个专注的“无效” 字段,看起来像:
http://postimg.org/image/9793bqcfj/
我尝试扩展 DefaultListCellRenderer 和 BasicComboBoxEditor 类,并将它们分别设置为组合框作为渲染器和编辑器。我的印象是编辑器将是我需要集中注意力的地方,因此在该类的 getEditorComponent 方法中,我将返回一个具有适当背景和前景的标签 - 但是在该方法中,我无法知道控件是否有焦点,所以无法确定我应该如何格式化返回的标签。此外,当我开始针对组合框设置编辑器时,我似乎完全失去了集中控制的能力——尽管这可能是我缺乏关于如何实现编辑器的知识。
我也一直在阅读有关 BasicComboBoxUI 的信息,但我遇到的任何解决方案都无法脱颖而出。
请有人给我指出正确的方向,我已经花了好几天的时间来修补这个,它真的开始困扰我了。请原谅netbeans生成的demo代码,只是为了让我快速拼凑一个demo。
package com.test;
import java.awt.*;
public class TestForm extends javax.swing.JFrame
public TestForm()
initComponents();
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//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();
// </editor-fold>//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
【问题讨论】:
请阅读此meta.stackexchange.com/questions/2950/… 并删除所有填充物和填充物。这是一个问答网站,而不是简奥斯汀角色的聊天:) 首先,我建议你看看How to Use the Focus Subsystem, Validating Input。问题是,选择颜色(您遇到问题)是由外观和感觉属性提供的,如果您尝试更改,将影响 UI 中的所有组件 您可能正在寻找ComboBox.selectionBackground
,寻找example。
@MadProgrammer - 谢谢,但我不确定使用 InputVerifier 是我想要采取的路线,因为我不想在填充每个字段时验证它们 - 我想给用户填写所有字段的机会,然后在他们尝试保存输入的详细信息时突出显示任何问题。如果我使用 InputVerifier,我假设用户实际上与其中一个控件进行交互 - 他们可能只是忽略了它并且没有填充任何值,我也需要满足这一点。
@trashgod - 正如 MadProgrammer 提到的,更改 ComboBox.selectionBackground 不会影响该类型的所有组件吗?我可能有一些包含有效值的组合框,在这种情况下,我希望这些控件具有通常的蓝色选择颜色 - 并且还有一些具有无效值的组合需要粉红色背景。关于如何为每个组件进行不同定义的任何想法?
【参考方案1】:
更新
忘了说。您遇到组合框着色问题的原因是您看到的颜色是选择颜色。颜色是在外观默认值中定义的,如果不编写自己的外观委托,就无法为单个组件更改这些颜色,我个人不会这样做
这是一个使用JXLayer
(现在是JLayer
,但我没有时间转换它)为无效字段提供突出显示的示例,虽然此示例确实使用了InputVerifer
API,但没有之所以必须这样做,它仅用于示例的一部分。验证后突出显示也很容易,重点是突出显示功能 - 而不是验证方法;)。
这是基于 Kirill Grouchnikov 在他的 Pushing Pixels 博客上提出的想法,Validation overlays using JXLayer
这是我前段时间做的一个想法的原型,代码仍然需要一些调整以提高性能,但在其他方面非常实用......我更喜欢内置对实时验证的支持......但是那只是我;)
主要测试类...
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<JPanel> 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
相关,突出显示 UI 层...
public class ValidationUI extends HighlightComponentUI
public ValidationUI()
super(Color.RED);
public class HighlightComponentUI extends AbstractLayerUI<JPanel>
private List<WeakReference<Component>> lstHighlights;
private Color highlightColor;
public HighlightComponentUI(Color highlight)
highlightColor = highlight;
lstHighlights = new ArrayList<WeakReference<Component>>(25);
protected void cleanReferences()
if (lstHighlights.size() > 0)
List<WeakReference<Component>> removed = new ArrayList<WeakReference<Component>>(lstHighlights.size());
for (WeakReference<Component> 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<Component> 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<Component>(comp));
setDirty(true);
public Component[] getHighlightedComponents()
List<Component> comps = new ArrayList<>(lstHighlights.size());
for (WeakReference<Component> 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<Component> toRemove = null;
for (WeakReference<Component> 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<? extends JPanel> 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<Component> 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
支持分离和更新了 UI 层和模型,但为了简单起见,我在这里将它们组合在一起......
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 filters
【讨论】:
以上是关于如何更改焦点 JComboBox 的突出显示颜色的主要内容,如果未能解决你的问题,请参考以下文章
如何更改 Java Swing TextArea 中的突出显示颜色?此外,更改与突出显示位置对应的文本的开头
从 JComboBox 弹出窗口中获取当前突出显示的项目(未选中的项目)