首先让我解释一下我想要实现的目标。我正在 Swing 中创建一个数据输入表单,由许多 JComboBox 和 JTextField 组成。验证例程迭代这些组件并确定为每个控件指定的值是否“有效”(验证的详细信息与本示例的目的无关)。
当例程识别出组件包含无效值时,我想更改 该字段的背景颜色,以及该字段的前景色/文本颜色 - 让用户清楚该字段存在问题。
如果字段被视为“有效”,我想将控件的背景设置为白色 - 并将前景/文本设置为黑色。
到目前为止,所有这些都非常简单,并且都可以在下面附加的演示代码中实现。
当组合框包含有效值并且获得焦点时 - 组合内编辑器的背景设置为蓝色,我对此非常满意。
但是,我想要实现的是当组合框包含无效值时更改用于突出显示聚焦组合框的颜色。尽管已将组合框的背景颜色更改为粉红色,但如果控件获得焦点,它仍然使用蓝色来指示它获得焦点。
聚焦的无效字段示例: http://postimg.org/image/ne9xgjch3/
虽然我意识到这是完全正常的行为,但我想做的是将用于突出显示“无效”字段之一的颜色更改为非聚焦和无效字段的较暗颜色控件将具有 - 这样用户仍然可以看到哪个控件获得焦点,并且它仍然是粉红色的。我明白这可能看起来很小,但我的最终用户坚持认为在聚焦时整个字段保持粉红色(或者更确切地说,不同色调的粉红色)。这就是我的乌托邦,一个专注而“无效”的地方 字段,看起来像:
http://postimg.org/image/9793bqcfj/
我尝试扩展 DefaultListCellRenderer 和 BasicComboBoxEditor 类,并将它们分别针对组合框设置为渲染器和编辑器。我的印象是编辑器将是我需要集中注意力的地方,因此在类的 getEditorComponent 方法中,我将返回一个具有适当背景和前景的标签 - 但是在该方法中,我无法知道控件是否具有焦点,因此无法确定应如何格式化返回的标签。此外,一旦我开始针对组合框设置编辑器,我似乎就完全失去了集中控制的能力 - 尽管这可能是因为我缺乏如何实现编辑器的知识。
我也一直在阅读有关 BasicComboBoxUI 的内容,但我遇到的没有一个解决方案是突出的。
请有人给我指明正确的方向,我花了好几天的时间来解决这个问题,这真的开始让我烦恼。请原谅 netbeans 生成的演示代码,它只是为了让我快速拼凑出一个演示。
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
}
最佳答案
更新
忘记说了。您在组合框的颜色方面遇到问题的原因是因为您看到的颜色是选择颜色。颜色是在外观默认值中定义的,如果不编写自己的外观委托(delegate),就无法更改单个组件的这些颜色,我个人不会这样做
这是使用 JXLayer
(现在是 JLayer
,但我还没有时间转换它)向无效字段提供突出显示的示例,而此示例确实使用 InputVerifer
API,没有理由必须这样做,它只是用于示例的一部分。进行验证后突出显示也很容易,焦点是突出显示功能 - 而不是验证方法;)。
这是基于 Kirill Grouchnikov 在他的 Pushing Pixels 博客 Validation overlays using JXLayer 上提出的想法
这是我前段时间所做的一个想法的原型(prototype),代码仍然需要一些调整来提高性能,但在其他方面相当实用......我更喜欢更好地内置支持实时验证......但是这就是我;)
主要测试类...
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();
}
}
验证模型相关类(我喜欢使用接口(interface)
和抽象
实现来为API提供灵活性并尽可能减少耦合)。我最初的原型(prototype)将 UI 层和模型分开并通过 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 filters
关于java - 如何更改聚焦 JComboBox 的突出显示颜色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25274566/