You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows L&F下JComboBox灰色背景实现原理及无受限依赖方案

问题描述

我们需要自定义JComboBox弹出框,通过匿名扩展实现自定义ComboPopup,但遇到以下矛盾:

  • 继承WindowsComboBoxUI时,JComboBox当前选中项背景为浅灰色(RGB约225,225,225),符合用户使用习惯;
  • 继承BasicComboBoxUI时,当前选中项背景为白色,与原生组件风格不一致;
  • Java 9之后WindowsComboBoxUI属于受限模块,现有Java 8项目升级会遭遇阻碍,因此必须避免引用该类及其所在包。

我们需要探究WindowsComboBoxUI实现选中项灰色背景的原理,并在自定义扩展中模拟该行为。

经调试发现:

  1. 绘制用的ListCellRenderer是透明的(opaque属性设为false);
  2. 其所在的CellRendererPane同样是透明的;
  3. 承载该面板的JComboBox不透明,背景色为白色(RGB255,255,255),按逻辑应显示白色背景,但实际并非如此。

在javax.swing.JComponent#paint方法中添加条件断点getClass().getName().contains("Renderer")后观察到:com.sun.java.swing.plaf.windows.WindowsComboBoxUI$WindowsComboBoxRenderer渲染时为透明状态,其父组件(渲染面板)也透明,祖父组件(JComboBox)不透明且背景为白色,但测试1中并未显示白色背景。

测试案例

测试1:效果符合预期,但引用了受限的com.sun包

package demos.combo;

import com.sun.java.swing.plaf.windows.WindowsComboBoxUI;

import javax.swing.*;
import javax.swing.plaf.ComboBoxUI;
import java.util.ArrayList;
import java.util.List;

public class SimpleComboDemo {
    public static void main(String[] args) throws UnsupportedLookAndFeelException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");

        JFrame frame = new JFrame("Simple combo demo");
        JPanel mainPanel = new JPanel();
        ComboBoxModel<String> model = new DefaultComboBoxModel<>();
        JComboBox<String> combo = new JComboBox<>(model);
        combo.setUI(comboUi());
        comboItems().forEach(combo::addItem);
        mainPanel.add(combo);
        frame.setContentPane(mainPanel);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static ComboBoxUI comboUi() {
        return new WindowsComboBoxUI() {
            // 假设此处需要重写方法,例如createPopup()
        };
    }

    private static List<String> comboItems() {
        List<String> items = new ArrayList<>();
        items.add("Pen");
        items.add("Pencil");
        items.add("Eraser");
        return items;
    }
}

[选中项为浅灰色背景的JComboBox]

测试2:未引用com.sun包,但背景为白色(前景色也为白色)

package demos.combo;

import di.ListCellRenderers;

import javax.swing.*;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.basic.BasicComboBoxUI;
import java.util.ArrayList;
import java.util.List;

public class SimpleComboDemo {
    public static void main(String[] args) throws UnsupportedLookAndFeelException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");

        JFrame frame = new JFrame("Simple combo demo");
        JPanel mainPanel = new JPanel();
        ComboBoxModel<String> model = new DefaultComboBoxModel<>();
        JComboBox<String> combo = new JComboBox<>(model);
        // 注意此行:必须设置自定义渲染器,因为BasicComboBoxRenderer的opaque=true,会被绘制成蓝色(List.selectionBackground)
        combo.setRenderer(ListCellRenderers.renderer(Object::toString));
        combo.setUI(comboUi());
        comboItems().forEach(combo::addItem);
        mainPanel.add(combo);
        frame.setContentPane(mainPanel);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static ComboBoxUI comboUi() {
        return new BasicComboBoxUI() {
            // 假设此处需要重写方法,例如createPopup()
            // 另外,若认为重写createRenderer()有用,实际并无效果
        };
    }

    private static List<String> comboItems() {
        List<String> items = new ArrayList<>();
        items.add("Pen");
        items.add("Pencil");
        items.add("Eraser");
        return items;
    }
}
package di;

import common.BaseListCellRenderer;

import javax.swing.*;
import java.util.function.Function;

public class ListCellRenderers {
    private ListCellRenderers() {
    }

    public static <T> ListCellRenderer<T> renderer(Function<T, String> stringFunction) {
        return new BaseListCellRenderer<T>() {
            @Override
            protected String getLabelText(T element) {
                return stringFunction.apply(element);
            }
        };
    }
}
package common;

import javax.swing.*;
import java.awt.*;
import java.util.Optional;

public abstract class BaseListCellRenderer<E> extends JLabel implements ListCellRenderer<E> {

    @Override
    public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus) {
        setColors(list, isSelected);
        setFont(list.getFont());
        setContent(value);
        determineTransparency(list, index);
        return this;
    }

    private void setColors(JList<? extends E> list, boolean isSelected) {
        if (isSelected)
            setSelectionColors(list);
        else
            setRegularColors(list);
    }

    private void setSelectionColors(JList<? extends E> list) {
        setBackground(list.getSelectionBackground());
        setForeground(list.getSelectionForeground());
    }

    private void setRegularColors(JList<? extends E> list) {
        setBackground(list.getBackground());
        setForeground(list.getForeground());
    }

    private void setContent(E value) {
        if (value instanceof Icon)
            setIcon((Icon) value);
        else {
            String nullableLabelText = getLabelText(value);
            String labelText = Optional.ofNullable(nullableLabelText).orElse("");
            setText(labelText);
        }
    }

    private void determineTransparency(JList<? extends E> list, int index) {
        boolean shouldBeTransparent = index == -1;
        if (shouldBeTransparent) {
            // 注意:此处标签不能设为opaque=true
            setOpaque(false);
            setForeground(list.getForeground());
        } else
            setOpaque(true);
    }

    protected abstract String getLabelText(E element);

    @Override
    public void addNotify() {
        super.addNotify();
    }
}

[选中项为白色背景的JComboBox]

测试3:与测试2相同,但未设置Windows外观

[选中项为透明背景的JComboBox]

目前尚未追踪到new Color(225, 225, 225)的创建或设置逻辑,不清楚该灰色背景的来源。


内容的提问来源于stack exchange,提问作者Wacage

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:24:58