Search code examples
javaswingjcomboboxlook-and-feel

LookAndFeel blocking JComboBox background change?


The goal is to change the background of all combo box when LookAndFeel is in the main method. But I get different results when LookAndFeel exists and not.

Without LookAndFeel: JComboBox is visible after JFrame resizing

enter image description here

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

import static java.awt.Color.WHITE;

public class TestFrame extends JFrame {

    private static final String[] ANIMALS = new String[]{"Cat", "Mouse", "Dog", "Elephant", "Bird", "Goat", "Bear"};

    public TestFrame() {
        setSize(600, 300);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        JComboBox<String> comboBox = new JComboBox<>();
        comboBox.setModel(new DefaultComboBoxModel<>(ANIMALS));
        comboBox.setForeground(WHITE);
        comboBox.setBackground(new Color(71, 81, 93));
        comboBox.getEditor().getEditorComponent().setBackground(new Color(71, 81, 93));
        comboBox.getEditor().getEditorComponent().setForeground(WHITE);
        comboBox.setRenderer(new DefaultListCellRenderer() {
            @Override
            public void paint(Graphics g) {
                setBackground(new Color(71, 81, 93));
                setForeground(WHITE);
                super.paint(g);
            }
        });
        panel.add(comboBox);
        add(panel);
    }

    public static void main(String[] args) {
        new TestFrame();
    }
}

With LookAndFeel:

enter image description here

import javax.swing.*;
import java.awt.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.awt.Color.WHITE;

public class TestFrame extends JFrame {

    private static final String[] ANIMALS = new String[]{"Cat", "Mouse", "Dog", "Elephant", "Bird", "Goat", "Bear"};

    public TestFrame() {
        setSize(600, 300);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        JComboBox<String> comboBox = new JComboBox<>();
        comboBox.setModel(new DefaultComboBoxModel<>(ANIMALS));
        comboBox.setForeground(WHITE);
        comboBox.setBackground(new Color(71, 81, 93));
        comboBox.getEditor().getEditorComponent().setBackground(new Color(71, 81, 93));
        comboBox.getEditor().getEditorComponent().setForeground(WHITE);
        comboBox.setRenderer(new DefaultListCellRenderer() {
            @Override
            public void paint(Graphics g) {
                setBackground(new Color(71, 81, 93));
                setForeground(WHITE);
                super.paint(g);
            }
        });
        panel.add(comboBox);
        add(panel);
    }

    public static void main(String[] args) {
        try {
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            Logger.getLogger(TestFrame.class.getName()).log(Level.SEVERE, null, ex);
        }

        /* Create and display the form */
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new TestFrame();
            }
        });
    }
}

How can I achieve full painting of the combo box with LookAndFeel enabled?


Solution

  • There is no thing like a disabled or enabled LookAndFeel. You always have a Look and feel set in your aplication. You just seem to set another LookAndFeel, which in your case is Nimbus. However, to answer your question, Nimbus sets the DefaultListCellRenderer's opacity-Property to false, (MetalLookAndFeel for example sets it to true) which is the reason for the visual representation you are showing. You should be able to fix this by overriding the getListCellRendererComponent method of DefaultListCellRenderer like so:

    @Override
    public Component getListCellRendererComponent(JList list, Object value,
                            int index, boolean isSelected, boolean cellHasFocus) {
    
        JComponent comp = (JComponent) super.getListCellRendererComponent(list,
                    value, index, isSelected, cellHasFocus);
    
        list.setBackground(COMBO_COLOR);
        list.setForeground(Color.WHITE);
        list.setOpaque(false);
            
        return comp;
    }
    

    You also have to set the UIManagers property ComboBox.forceOpaque to false, like so:

    UIManager.put("ComboBox.forceOpaque", false);
    

    A full list of the Nimubs defauts can be found here.

    A full working example of the fixed problem if needed:

    import java.awt.*;
    import java.util.Arrays;
    import javax.swing.*;
    
    public class JComboBoxExample {
        
        private static final Color COMBO_COLOR = new Color(71, 81, 93);
        private static final String[] COMBO_DATA = {"Get back!", "Go!", "Help!", "Careful!"};
        
        public static void main(String[] args) throws Exception {        
            String nimbus = Arrays.asList(UIManager.getInstalledLookAndFeels())
                    .stream()
                    .filter(i -> i.getName().equals("Nimbus"))
                    .findFirst()
                    .get()
                    .getClassName();
            
            UIManager.setLookAndFeel(nimbus);
            
            UIManager.put("ComboBox.forceOpaque", false);
            
            JFrame jf = new JFrame();
            jf.setVisible(true);
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jf.setLocationRelativeTo(null);
            
            MyComboBox comboBox = new MyComboBox(new DefaultComboBoxModel(COMBO_DATA));
            jf.add(comboBox);
            jf.pack();
        }
        
        private static class MyComboBox extends JComboBox  {
    
            public MyComboBox(DefaultComboBoxModel model) {
                super(model);
                setForeground(Color.WHITE);
                setFont(new Font("Arial", Font.PLAIN, 30));
                setPreferredSize(new Dimension(350, 50));
                setRenderer(new MyRenderer());
            }
    
        }
    
        private static class MyRenderer extends DefaultListCellRenderer {
            
            @Override
            public Component getListCellRendererComponent(JList list, Object value,
                                int index, boolean isSelected, boolean cellHasFocus) {
    
                JComponent comp = (JComponent) super.getListCellRendererComponent(list,
                        value, index, isSelected, cellHasFocus);
    
                list.setBackground(COMBO_COLOR);
                list.setForeground(Color.WHITE);
                list.setOpaque(false);
                
                return comp;
            }
        }
        
    }
    

    results in:

    enter image description here

    By the way, I figured this out by using the visual debugger of the NetBeans IDE.