Home > database >  JComboBox custom renderer generics
JComboBox custom renderer generics

Time:06-18

I have written a renderer for JComboBoxes to replace the milky colour when in disabled state by a more legible one.

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

public class RendererDemo extends JFrame {
  public static final long serialVersionUID = 100L;

  String[] items= {"one", "two", "three"};


  public RendererDemo() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(300, 100);

    JComboBox<Object> cmb = new JComboBox<>(items);
//    JComboBox<String> cmb = new JComboBox<>(items);
    cmb.setRenderer(new DisabledDarkTextRenderer(cmb));
    add(cmb, BorderLayout.NORTH);

    JCheckBox check = new JCheckBox("Combo enabled", true);
    add(check, BorderLayout.SOUTH);
    check.addActionListener(e -> cmb.setEnabled(check.isSelected()));

    setVisible(true);
  }


  static public void main(String args[]) {
    EventQueue.invokeLater(RendererDemo::new);
  }


  class DisabledDarkTextRenderer extends DefaultListCellRenderer {
    public static final long serialVersionUID = 50378L;

    JComboBox<Object> cmb;
    ListCellRenderer<Object> origRenderer;
    Object defaultResource= UIManager.get("ComboBox.disabledForeground");

    public DisabledDarkTextRenderer(JComboBox<Object> cmb) {
      this.cmb= cmb;
      origRenderer= cmb.getRenderer();
    }

    public Component getListCellRendererComponent(JList<?> list, Object value,
                        int index, boolean isSelected, boolean cellHasFocus) {
      Component orig= origRenderer.getListCellRendererComponent(list, value,
                                            index, isSelected, cellHasFocus);
      if (cmb.isEnabled())
        UIManager.put("ComboBox.disabledForeground", defaultResource);
      else
        UIManager.put("ComboBox.disabledForeground", new ColorUIResource
                                                            (Color.GRAY));
      return orig;
    }
  }

}

However, when I declare the JComboBox as being of type <String>, the code fails to compile due to "incompatible types: ListCellRenderer<CAP#1> cannot be converted to ListCellRenderer<Object> ..."

So I tried to make the renderer more general. From all my attempts to modify the generic type, the following version is the one with only one error left. It's again the above mentioned error, only that it reads now: "incompatible types: JList<CAP#1> cannot be converted to JList<? extends CAP#2> ...".

How to fix this?

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

public class RendererDemo2 extends JFrame {
  public static final long serialVersionUID = 100L;

  String[] items= {"one", "two", "three"};


  public RendererDemo2() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(300, 100);

    JComboBox<String> cmb = new JComboBox<>(items);
    cmb.setRenderer(new DisabledDarkTextRenderer2(cmb));
    add(cmb, BorderLayout.NORTH);

    JCheckBox check = new JCheckBox("Combo enabled", true);
    add(check, BorderLayout.SOUTH);
    check.addActionListener(e -> cmb.setEnabled(check.isSelected()));

    setVisible(true);
  }


  static public void main(String args[]) {
    EventQueue.invokeLater(RendererDemo2::new);
  }


  class DisabledDarkTextRenderer2 extends DefaultListCellRenderer {
    public static final long serialVersionUID = 50379L;

    JComboBox<?> cmb;
    ListCellRenderer<?> origRenderer;
    Object defaultResource= UIManager.get("ComboBox.disabledForeground");

    public DisabledDarkTextRenderer2(JComboBox<?> cmb) {
      this.cmb= cmb;
      origRenderer= cmb.getRenderer();
    }

    public Component getListCellRendererComponent(JList<?> list, Object value,
            int index, boolean isSelected, boolean cellHasFocus) {
      Component orig= origRenderer.getListCellRendererComponent(list, value,
                    index, isSelected, cellHasFocus);
      if (cmb.isEnabled())
        UIManager.put("ComboBox.disabledForeground", defaultResource);
      else
        UIManager.put("ComboBox.disabledForeground", new ColorUIResource
                                                            (Color.GRAY));
      return orig;
    }
  }

}

Following MadProgrammer's advice my renderer now looks like this:

  class DisabledDarkTextRenderer2<T> extends DefaultListCellRenderer {
    public static final long serialVersionUID = 50379L;

    JComboBox<T> cmb;
    ListCellRenderer<?> origRenderer;
    Object defaultResource= UIManager.get("ComboBox.disabledForeground");

    public DisabledDarkTextRenderer2(JComboBox<T> cmb) {
      this.cmb= cmb;
      origRenderer= cmb.getRenderer();
    }

    public Component getListCellRendererComponent(JList<?> list, Object value,
            int index, boolean isSelected, boolean cellHasFocus) {
      Component orig= origRenderer.getListCellRendererComponent(list, value,
                    index, isSelected, cellHasFocus);
      if (cmb.isEnabled())
        UIManager.put("ComboBox.disabledForeground", defaultResource);
      else
        UIManager.put("ComboBox.disabledForeground", new ColorUIResource
                                                            (Color.GRAY));
      return orig;
    }
  }

but the compile time error persists.
I am using this renderer for combos which change their state at runtime. So I am free from caring in each program to set the colour when needed, as the renderer is doing this automatically for me.

CodePudding user response:

You need to "genericfy" your DisabledDarkTextRenderer2, as an example...

class DisabledDarkTextRenderer2<T> implements ListCellRenderer<T> {
    public static final long serialVersionUID = 50379L;

    private JComboBox<T> combobox;
    private ListCellRenderer<? super T> origRenderer;

    public DisabledDarkTextRenderer2(JComboBox<T> combobox) {
        this.combobox = combobox;
        this.origRenderer = combobox.getRenderer();
    }
    
    public static <T> void install(JComboBox<T> combobox) {
        combobox.setRenderer(new DisabledDarkTextRenderer2<T>(combobox));
    }

    @Override
    public Component getListCellRendererComponent(JList<? extends T> list, T value, int index, boolean isSelected, boolean cellHasFocus) {
        Component orig = origRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        if (combobox.isEnabled()) {
            orig.setForeground(null);
        } else {
            orig.setForeground(Color.GRAY);
        }
        return orig;
    }
}

Now, having said that, I would discourage you from using UIManager within the renderer, as this will effect ALL components rendered after the change, which may or may not be your desired goal, if so, then just set the value at the start of the application.

The following example will make the default disabled foreground color RED for all comboboxes without you having to do anything else at all.

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.ColorUIResource;

public final class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                UIManager.put("ComboBox.disabledForeground", new ColorUIResource(Color.RED));
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {
        public TestPane() {
            setLayout(new GridBagLayout());
            setBorder(new EmptyBorder(32, 32, 32, 32));

            JComboBox<String> cmb = new JComboBox<>(new String[]{"Hello", "Goodbye"});
            cmb.setSelectedIndex(0);

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            add(cmb, gbc);

            JButton toggle = new JButton("Toggle");
            toggle.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    cmb.setEnabled(!cmb.isEnabled());
                }
            });
            add(toggle, gbc);
        }
    }

}
  • Related