2

We are working on a Swing application with Nimbus LaF. We have changed many of the Nimbus defaults (control, text, NimbusLightBackground and so on) to have a dark theme.

Now we have great trouble with the rendering of JLists and JComboBoxes, because the renderer apparently uses the NimbusLightBackground color for the selected text foreground. This results in dark grey text on dark blue background - not good.

I have tried overriding any applicable-seeming key in Nimbus Defaults ("ComboBox:\"ComboBox.listRenderer\"[Selected].textForeground" and suchlike) both globally via UIManager.putDefault() and per-component-overrides, but simply cannot get any change.

Even SwingX Highlighters can't seem to override this behaviour, at least in the combobox dropdown.

Any ideas on how to set the selected text foreground color for J(X)List and J(X)ComboBox Dropdowns?

My latest attempt at per-component-override:

JXComboBox comboBox = new JXComboBox();
UIDefaults comboBoxTheme = new UIDefaults();
comboBoxTheme.put("nimbusLightBackground", new Color(0xFFFAFA));
comboBoxTheme.put("ComboBox:\"ComboBox.listRenderer\"[Selected].textForeground", new Color(0xFFFAFA));
comboBox.putClientProperty("Nimbus.Overrides.InheritDefaults", true);
comboBox.putClientProperty("Nimbus.Overrides", comboBoxTheme);
SwingUtilities.updateComponentTreeUI(comboBox);

And the application-wide nimbus defaults:

ColorUIResource backgroundUI = new ColorUIResource(0x494949);
ColorUIResource textUI = new ColorUIResource(0xFFFAFA);
ColorUIResource controlBackgroundUI = new ColorUIResource(0x5F5F4D);
ColorUIResource infoBackgroundUI = new ColorUIResource(0x2f5cb4);
ColorUIResource infoUI = new ColorUIResource(0x2f5cb4);
ColorUIResource lightBackgroundUI = new ColorUIResource(0x5D5D5B);
ColorUIResource focusUI = new ColorUIResource(0x39698a);

UIManager.put("control", backgroundUI);
UIManager.put("text", textUI);
UIManager.put("nimbusLightBackground", lightBackgroundUI);
UIManager.put("info", infoUI);
UIManager.put("nimbusInfoBlue", infoBackgroundUI);
UIManager.put("nimbusBase", controlBackgroundUI);
UIManager.put("nimbusBlueGrey", controlBackgroundUI);
UIManager.put("nimbusFocus", focusUI);

All implemented in Java 7u55, though I doubt that matters as nobody seems to have maintained Swing/Nimbus for quite some time.

PS: I have, of course, read this question and others, but have not found an answer that works.

EDIT: here is an SSCCE that demonstrates the problem. It creates a JFrame with a default-only combobox on top, a list in the middle and a per-component-overriden combobox at the bottom. The problem can be seen when selecting a value in the list or from the box dropdowns.

package sscce;

import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.plaf.ColorUIResource;

public class ForegroundProblemDemo extends JFrame {

public ForegroundProblemDemo() {
    super("Demo");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JComboBox<String> comboBoxWithDefaults = createComboBox();
    JComboBox<String> comboBoxWithOverrides = createComboBox();
    JList<String> list = createList();
    addOverrides(comboBoxWithOverrides);

    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(new JScrollPane(list), BorderLayout.CENTER);
    getContentPane().add(comboBoxWithDefaults, BorderLayout.NORTH);
    getContentPane().add(comboBoxWithOverrides, BorderLayout.SOUTH);

    pack();
    setLocationRelativeTo(null);
    setVisible(true);
}

JComboBox<String> createComboBox() {
    JComboBox<String> comboBox = new JComboBox<>(new String[] {"A","B","C","D"});
    return comboBox;
}

JList<String> createList() {
    JList<String> list = new JList<>(new String[] {"A","B","C","D"});
    return list;
}

void addOverrides(JComponent component) {
    UIDefaults theme = new UIDefaults();
    theme.put("nimbusLightBackground", new Color(0xFFFAFA));
    theme.put("ComboBox:\"ComboBox.listRenderer\"[Selected].textForeground", new Color(0xFFFAFA));
    component.putClientProperty("Nimbus.Overrides.InheritDefaults", true);
    component.putClientProperty("Nimbus.Overrides", theme);
    SwingUtilities.updateComponentTreeUI(component);
}


public static void main(String... args) throws Throwable {
    ColorUIResource backgroundUI = new ColorUIResource(0x494949);
    ColorUIResource textUI = new ColorUIResource(0xFFFAFA);
    ColorUIResource controlBackgroundUI = new ColorUIResource(0x5F5F4D);
    ColorUIResource infoBackgroundUI = new ColorUIResource(0x2f5cb4);
    ColorUIResource infoUI = new ColorUIResource(0x2f5cb4);
    ColorUIResource lightBackgroundUI = new ColorUIResource(0x5D5D5B);
    ColorUIResource focusUI = new ColorUIResource(0x39698a);
    UIManager.put("control", backgroundUI);
    UIManager.put("text", textUI);
    UIManager.put("nimbusLightBackground", lightBackgroundUI);
    UIManager.put("info", infoUI);
    UIManager.put("nimbusInfoBlue", infoBackgroundUI);
    UIManager.put("nimbusBase", controlBackgroundUI);
    UIManager.put("nimbusBlueGrey", controlBackgroundUI);
    UIManager.put("nimbusFocus", focusUI);

    for (LookAndFeelInfo lafInfo : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(lafInfo.getName())) {
            UIManager.setLookAndFeel(lafInfo.getClassName());
            break;
        }
    }

    new ForegroundProblemDemo();
}

}

EDIT 2: Sorry, should have mentioned this before: For the list the problem is easily resolved with the setSelectionForeground() method. For ComboBoxes, I have yet to find a way short of custom renderers. So my main focus here is on the ComboBoxes.

Community
  • 1
  • 1
Mike Adler
  • 1,199
  • 9
  • 24
  • 2
    1. standard Renderers concept works in Nimbus too, could be (in Java7) bugs sensitive, 2. did you tried custom L&F(s) based on Nimbus, there is used own injection to all keys in UIManager, overrode part of bugs, or unaccesible keys, 3. +1 for description, but -1 without an SSCCE/MCVE – mKorbel Jun 12 '14 at 08:28
  • Thanks for the blazingly fast response. I have added the SSCCE. 1. I really want to avoid to rewrite all renderers - mostly I use the defaults and do some highlighting with SwingX's highlighters. Might be worth a test, though. 2. I have not extended a custom l&f off of Nimbus, but just used it out of the box with some UIManager default changes. Project timeframe really doesn't allow for what I suspect is quite a lengthy and complex task such as writing a completely new L&F. I think the problem with Nimbus is with the ComboBoxUI hidden deep in rt.jar and rewriting it seems ... cumbersome. – Mike Adler Jun 12 '14 at 09:24
  • thanks (+1) for an SSCCE, [most of overrides at runtime required, it should be ...](http://stackoverflow.com/questions/9958004/change-font-at-runtime), again to look at custom L&F based on Nimbus, there is solved a few issues :-) – mKorbel Jun 12 '14 at 10:42
  • use Renderer as primary decorator (probably to override BasicComboBoxRenderer), otherwise you have to use own Painter (not sure how and if is works in Java8) – mKorbel Jun 12 '14 at 10:43
  • Okay, so it might be necessary to either write custom renderers or even a custom L&F. What really bugs me about it, though, is that Nimbus actually does provide ComboBox-related defaults which simply DO NOT work. Am I doing something wrong there? ComboBox:\"ComboBox.listRenderer\"[Selected].textForeground is listed in the Nimbus Defaults page but does not seem to have any effect - as do most other ComboBox-related keys there. Is Nimbus broken or am I doing it wrong? – Mike Adler Jun 13 '14 at 05:52
  • I'll try to reproduce possible options, note there is editable and non_editable (is required to override the Editor, there is derived JTextField, JFormattedTextField) – mKorbel Jun 13 '14 at 06:45

2 Answers2

2

I do not like this answer and will gladly accept any one that tells me how it works with just Nimbus settings

What I have ended up doing - and will be delighted to change if I find a better solution - is this:

I have created a ListCellRenderer implementation that wraps the DefaultListCellRenderer and sets the foreground color iff the isSelected parameter is true. It works, but I do not like it one bit for these reasons:

  1. Every other custom renderer (thankfully not many) will have to implement the same hack, which violates the DRY principle.
  2. This overrides the LookAndFeel in such a way that setting another theme (or simply changing back to light theme) will require changes in multiple places in the code.
  3. Every instance of JList or JComboBox will have to set the renderer manually, which again violates the DRY principle.
  4. It is an ugly workaround for a bug in the Java core libraries and I am furious that despite multiple bug reports to that topic nothing seems to have happened since 2007.

Anyway, here's the code for those lonely wanderers who may have the same problem.

import java.awt.Color;
import java.awt.Component;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JList;
import javax.swing.ListCellRenderer;


@SuppressWarnings("rawtypes")
public class ThemeCompliantListCellRenderer implements ListCellRenderer {

private ListCellRenderer wrappedRenderer = new DefaultListCellRenderer();
private Color textColor = new Color(0xFFFAFA); 

@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
    @SuppressWarnings("unchecked")
    Component c = wrappedRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
    if (isSelected) {
        c.setForeground(textColor);
    }
    return c;
}

public void setSelectedForeground(Color color) {
    textColor = color;
}
}
Mike Adler
  • 1,199
  • 9
  • 24
  • +1 success is one key in three (from top to bottom), better is in Java < 1.6_20 – mKorbel Jun 13 '14 at 07:29
  • Still not happy with it, though. For instance, it breaks the SwingX DefaultListRenderer(StringValue x) method of creating quick and easy renderers. – Mike Adler Jun 13 '14 at 07:49
  • Okay, as the use case now popped up: Re-Enabling SwingX StringValue parsing is, of course, easy to add: I added a field of type StringValue, preinitialized to StringValues.TO_STRING, put in the appropriate Setter and extended the getListCellRendererComponent method with the following, no less ugly hack: if (c instanceof JLabel) ((JLabel) c).setText(stringValue.getString(value)); return c; Still don't like it. – Mike Adler Jun 30 '14 at 13:56
  • this must be caused with CPU consuption – mKorbel Jun 30 '14 at 15:40
  • Should not be too bad. As long as the StringValue isn't expensive, we only have the instance of and a single assignment. With only the TO_STRING implementation it might even be compiled away. Not sure, though. Would be good if someone with good knowledge of the java jit compilation process could clarify. – Mike Adler Jun 30 '14 at 16:19
  • whatever setXxx inside XxxRenderer could be CPU/GPU intesive, but painted is (only inside) Rectangle visible in the JViewport, valid for JComponents where is implemented Scrollable (in API/programatically) – mKorbel Jul 01 '14 at 06:38
  • Okay, I think I see your point. But in this case it shouldn't be (much) more expensive than using SwingX-Renderers anyway. Resizing issues should be cleared up when I use appropriate prototype values for the cells. – Mike Adler Jul 01 '14 at 06:46
  • have to test [Combo Box Popup by @camickr](http://tips4java.wordpress.com/?s=jcombobox) instead of to setPrototypeValue or decorators, there are a few very good ideas with excelent code (most of methods can be used together with SwingX APIs) – mKorbel Jul 01 '14 at 06:56
2
  • see whats happens, Win8.1 64b, Java7, JDK 1.7_021, by using full rectangle Painter,

enter image description here

import java.awt.*;
import java.util.Vector;
import javax.swing.*;
import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.nimbus.AbstractRegionPainter;

public class MyComboBox {

    private Vector<String> listSomeString = new Vector<String>();
    private JComboBox someComboBox = new JComboBox(listSomeString);
    private JComboBox editableComboBox = new JComboBox(listSomeString);
    private JComboBox non_EditableComboBox = new JComboBox(listSomeString);
    private JFrame frame;

    public MyComboBox() {
        listSomeString.add("-");
        listSomeString.add("Snowboarding");
        listSomeString.add("Rowing");
        listSomeString.add("Knitting");
        listSomeString.add("Speed reading");
        someComboBox.setPrototypeDisplayValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        someComboBox.setFont(new Font("Serif", Font.BOLD, 16));
        someComboBox.setEditable(true);
        someComboBox.getEditor().getEditorComponent().setBackground(Color.YELLOW);
        ((JTextField) someComboBox.getEditor().getEditorComponent()).setBackground(Color.YELLOW);
        editableComboBox.setPrototypeDisplayValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        editableComboBox.setFont(new Font("Serif", Font.BOLD, 16));
        editableComboBox.setEditable(true);
        JTextField text = ((JTextField) editableComboBox.getEditor().getEditorComponent());
        text.setBackground(Color.YELLOW);
        /*JComboBox coloredArrowsCombo = editableComboBox;
         Component[] comp = coloredArrowsCombo.getComponents();
         for (int i = 0; i < comp.length; i++) {
         if (comp[i] instanceof MetalComboBoxButton) {
         MetalComboBoxButton coloredArrowsButton = (MetalComboBoxButton) comp[i];
         coloredArrowsButton.setBackground(null);
         break;
         }
         }*/
        non_EditableComboBox.setPrototypeDisplayValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        non_EditableComboBox.setFont(new Font("Serif", Font.BOLD, 16));
        frame = new JFrame();
        frame.setLayout(new GridLayout(0, 1, 10, 10));
        frame.add(someComboBox);
        frame.add(editableComboBox);
        frame.add(non_EditableComboBox);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocation(100, 100);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        try {
            for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(laf.getName())) {
                    UIManager.setLookAndFeel(laf.getClassName());
                    UIManager.getLookAndFeelDefaults().put("ComboBox[Enabled].backgroundPainter",
                            new javax.swing.plaf.nimbus.AbstractRegionPainter() {
                        @Override
                        protected AbstractRegionPainter.PaintContext getPaintContext() {
                            return new AbstractRegionPainter.PaintContext(null, null, false);
                        }

                        @Override
                        protected void doPaint(Graphics2D g, JComponent c,
                                int width, int height, Object[] extendedCacheKeys) {
                            g.setColor(Color.MAGENTA);
                            g.fill(new Rectangle(0, 0, width, height));
                        }
                    });
                    UIManager.getLookAndFeelDefaults().put("ComboBox[Focused+Pressed].backgroundPainter",
                            new javax.swing.plaf.nimbus.AbstractRegionPainter() {
                        @Override
                        protected AbstractRegionPainter.PaintContext getPaintContext() {
                            return new AbstractRegionPainter.PaintContext(null, null, false);
                        }

                        @Override
                        protected void doPaint(Graphics2D g, JComponent c,
                                int width, int height, Object[] extendedCacheKeys) {
                            g.setColor(Color.CYAN);
                            g.fill(new Rectangle(0, 0, width, height));
                        }
                    });
                    UIManager.getLookAndFeelDefaults().put("ComboBox[Focused].backgroundPainter",
                            new javax.swing.plaf.nimbus.AbstractRegionPainter() {
                        @Override
                        protected AbstractRegionPainter.PaintContext getPaintContext() {
                            return new AbstractRegionPainter.PaintContext(null, null, false);
                        }

                        @Override
                        protected void doPaint(Graphics2D g, JComponent c,
                                int width, int height, Object[] extendedCacheKeys) {
                            g.setColor(Color.RED);
                            g.fill(new Rectangle(0, 0, width, height));
                        }
                    });
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyComboBox aCTF = new MyComboBox();
            }
        });
    }
}
  • nothing happends without using an XxxRenderer, then is possible to create nice theme for editable and non_editable JComboBox (with the same Color theme)

  • I'd be look at Seaglas L&F (note to required compiled in JDK 1.6._Xxx)

enter image description here

import java.awt.*;
import java.util.Vector;
import javax.swing.*;
import javax.swing.UIManager;

public class MyComboBox {

    private Vector<String> listSomeString = new Vector<String>();
    private JComboBox someComboBox = new JComboBox(listSomeString);
    private JComboBox editableComboBox = new JComboBox(listSomeString);
    private JComboBox non_EditableComboBox = new JComboBox(listSomeString);
    private JFrame frame;

    public MyComboBox() {
        listSomeString.add("-");
        listSomeString.add("Snowboarding");
        listSomeString.add("Rowing");
        listSomeString.add("Knitting");
        listSomeString.add("Speed reading");
        someComboBox.setPrototypeDisplayValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        someComboBox.setFont(new Font("Serif", Font.BOLD, 16));
        someComboBox.setEditable(true);
        someComboBox.getEditor().getEditorComponent().setBackground(Color.YELLOW);
        ((JTextField) someComboBox.getEditor().getEditorComponent()).setBackground(Color.YELLOW);
        editableComboBox.setPrototypeDisplayValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        editableComboBox.setFont(new Font("Serif", Font.BOLD, 16));
        editableComboBox.setEditable(true);
        JTextField text = ((JTextField) editableComboBox.getEditor().getEditorComponent());
        text.setBackground(Color.YELLOW);
        non_EditableComboBox.setPrototypeDisplayValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        non_EditableComboBox.setFont(new Font("Serif", Font.BOLD, 16));
        frame = new JFrame();
        frame.setLayout(new GridLayout(0, 1, 10, 10));
        frame.add(someComboBox);
        frame.add(editableComboBox);
        frame.add(non_EditableComboBox);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocation(100, 100);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel("com.seaglasslookandfeel.SeaGlassLookAndFeel");
        } catch (Exception e) {
            e.printStackTrace();
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyComboBox aCTF = new MyComboBox();
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • Sorry I took so long. This is not quite what I was hoping for, but it looks like a feasible way to work around what I'm now certain is a major bug in Nimbus. Thank you for your effort! – Mike Adler Jun 18 '14 at 16:05