Search code examples
javaswingjcombobox

Centre items in a JComboBox in addition to displaying text clearly when it is disabled


I'm using

((JLabel)comboBox.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);

to centre text items in my JComboBox.

But I also would like the text to appear clearly when the ComboBox is disabled (i.e. the text appearance should not/barely change).

By solution was to use comboBox.setEditable(true) (see below for more) when I use comboBox.setVisible(false) which works well except that it aligns the text back to the left. And it seems that fixing this issue would require a lot of work.

I'm wondering if someone can suggest another way to display the text clearly in a disabled comboBox?

Many thanks.

Edit: The full code I'm using to improve the disabled text display is

        ComboBoxEditor editor = comboBox.getEditor();
        JTextField etf = (JTextField) editor.getEditorComponent();
        etf.setDisabledTextColor(UIManager.getColor("ComboBox.foreground"));
        etf.setBackground(UIManager.getColor("ComboBox.background"));
        comboBox.setEnabled(false);
        comboBox.setEditable(true);

Edit 2:

Here is a full example:

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;


public class GUITest extends JPanel {

    static JFrame frame;
    static JButton changeButton;
    static JComboBox exampleComboBox;

    public GUITest() {
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
        String[] examples = {
            "abcd",
            "efgh",
            "ij"
        };

        exampleComboBox = new JComboBox(examples);

        // centre text of items in combo box
        ((JLabel) exampleComboBox.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);

        JLabel buttonLabel = new JLabel("Click to make combo box disabled but text show clearly",
                JLabel.LEADING); //== LEFT

        JPanel patternPanel = new JPanel();
        patternPanel.setLayout(new BoxLayout(patternPanel,
                BoxLayout.PAGE_AXIS));

        exampleComboBox.setAlignmentX(Component.LEFT_ALIGNMENT);
        patternPanel.add(exampleComboBox);

        JPanel resultPanel = new JPanel(new GridLayout(0, 1));
        resultPanel.add(buttonLabel);
        changeButton = new JButton();
        resultPanel.add(changeButton);
        addButtonListener();

        patternPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        resultPanel.setAlignmentX(Component.LEFT_ALIGNMENT);

        add(patternPanel);
        add(Box.createRigidArea(new Dimension(0, 10)));
        add(resultPanel);

        setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    } //constructor

    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("ComboBoxDemo2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        JComponent newContentPane = new GUITest();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    private static void addButtonListener() {
        changeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                ComboBoxEditor editor = exampleComboBox.getEditor();
                JTextField etf = (JTextField) editor.getEditorComponent();
                etf.setDisabledTextColor(UIManager.getColor("ComboBox.foreground"));
                etf.setBackground(UIManager.getColor("ComboBox.background"));
                exampleComboBox.setEnabled(false);
                exampleComboBox.setEditable(true);
            }
        });
    }

    public static void main(String[] args) {

        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

Edit 3: It's working now but the text moves up slightly and the comboBox border is removed when the button is clicked. Is there a way to prevent this?

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;


public class GUITest extends JPanel {

    static JFrame frame;
    static JButton changeButton;
    static JComboBox exampleComboBox;

    public GUITest() {
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
        String[] examples = {
            "abcd",
            "efgh",
            "ij"
        };

        exampleComboBox = new JComboBox(examples);
        exampleComboBox.setEditor(new TextPaneComboBoxEditor());

        // centre text of items in combo box
        ((JLabel) exampleComboBox.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);

        JLabel buttonLabel = new JLabel("Click to make combo box disabled but text show clearly",
                JLabel.LEADING); //== LEFT

        JPanel patternPanel = new JPanel();
        patternPanel.setLayout(new BoxLayout(patternPanel,
                BoxLayout.PAGE_AXIS));

        exampleComboBox.setAlignmentX(Component.LEFT_ALIGNMENT);
        patternPanel.add(exampleComboBox);

        JPanel resultPanel = new JPanel(new GridLayout(0, 1));
        resultPanel.add(buttonLabel);
        changeButton = new JButton();
        resultPanel.add(changeButton);
        addButtonListener();

        patternPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        resultPanel.setAlignmentX(Component.LEFT_ALIGNMENT);

        add(patternPanel);
        add(Box.createRigidArea(new Dimension(0, 10)));
        add(resultPanel);

        setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    } //constructor

    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("ComboBoxDemo2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        JComponent newContentPane = new GUITest();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    private static void addButtonListener() {
        changeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                ComboBoxEditor editor = exampleComboBox.getEditor();
                JTextPane etf = (JTextPane) editor.getEditorComponent();
                etf.setDisabledTextColor(UIManager.getColor("ComboBox.foreground"));
                etf.setBackground(UIManager.getColor("ComboBox.background"));
                exampleComboBox.setEnabled(false);
                exampleComboBox.setEditable(true);
            }
        });
    }

    public static void main(String[] args) {

        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

class TextPaneComboBoxEditor implements ComboBoxEditor {
    protected JTextPane editor;
    private Object oldValue;

    public TextPaneComboBoxEditor() {
        editor = createEditorComponent();
    }

    public Component getEditorComponent() {
        return editor;
    }

    /**
     * Creates the internal editor component. Override this to provide
     * a custom implementation.
     *
     * @return a new editor component
     * @since 1.6
     */
    protected JTextPane createEditorComponent() {
        JTextPane editor = new BorderlessTextPane("",9);
        editor.setBorder(null);

        SimpleAttributeSet center = new SimpleAttributeSet();
        StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
        StyledDocument doc = editor.getStyledDocument();
        doc.setParagraphAttributes(0, doc.getLength(), center, false);

        return editor;
    }

    /**
     * Sets the item that should be edited.
     *
     * @param anObject the displayed value of the editor
     */
    public void setItem(Object anObject) {
        String text;

        if ( anObject != null )  {
            text = anObject.toString();
            if (text == null) {
                text = "";
            }
            oldValue = anObject;
        } else {
            text = "";
        }

        // workaround for 4530952
        if (! text.equals(editor.getText())) {
            editor.setText(text);
        }

    }

    public Object getItem() {
        Object newValue = editor.getText();

        // This code only works for Strings. The default implementation would
        // use reflection to create Object of whatever class was stored in the
        // ComboBoxModel. You will need to fix the reflection code if you want
        // to support other types of data in the model

/*
        if (oldValue != null && !(oldValue instanceof String))  {
            // The original value is not a string. Should return the value in it's
            // original type.
            if (newValue.equals(oldValue.toString()))  {
                return oldValue;
            } else {
                // Must take the value from the editor and get the value and cast it to the new type.
                Class<?> cls = oldValue.getClass();
                try {
                    Method method = MethodUtil.getMethod(cls, "valueOf", new Class[]{String.class});
                    newValue = MethodUtil.invoke(method, oldValue, new Object[] { editor.getText()});
                } catch (Exception ex) {
                    // Fail silently and return the newValue (a String object)
                }
            }
        }
*/
        return newValue;
    }

    public void selectAll() {
        editor.selectAll();
        editor.requestFocus();
    }

    public void addActionListener(ActionListener l) {
//        editor.addActionListener(l);

        Action enter = new WrappedActionListener(l);
        editor.getActionMap().put("insert-break", enter);
    }

    public void removeActionListener(ActionListener l) {
//        editor.removeActionListener(l);
    }

    static class BorderlessTextPane extends JTextPane {
        public BorderlessTextPane(String value,int n) {
//            super(value,n);
            setText(value);
        }

        // workaround for 4530952
        public void setText(String s) {
            if (getText().equals(s)) {
                return;
            }
            super.setText(s);
        }

        public void setBorder(Border b) {
            if (!(b instanceof UIResource)) {
                super.setBorder(b);
            }
        }
    }

    /**
     * A subclass of TextPaneComboBoxEditor that implements UIResource.
     * TextPaneComboBoxEditor doesn't implement UIResource
     * directly so that applications can safely override the
     * cellRenderer property with TextPaneListCellRenderer subclasses.
     * <p>
     * <strong>Warning:</strong>
     * Serialized objects of this class will not be compatible with
     * future Swing releases. The current serialization support is
     * appropriate for short term storage or RMI between applications running
     * the same version of Swing.  As of 1.4, support for long term storage
     * of all JavaBeans&trade;
     * has been added to the <code>java.beans</code> package.
     * Please see {@link java.beans.XMLEncoder}.
     */
    public static class UIResource extends TextPaneComboBoxEditor
    implements javax.swing.plaf.UIResource {
    }

    static class WrappedActionListener extends AbstractAction
    {
        private ActionListener listener;

        public WrappedActionListener(ActionListener listener)
        {
            this.listener = listener;
        }

        @Override
        public void actionPerformed(ActionEvent e)
        {
            listener.actionPerformed(e);
        }
    }
}

Solution

  • There is no need to play with the editor when you disable the combo box. Just disable the combo box. So the code in the ActionListener is:

    changeButton.addActionListener(new ActionListener() 
    {
        @Override
        public void actionPerformed(ActionEvent e) 
        {
            exampleComboBox.setEnabled(false);
            //exampleComboBox.setEditable(true);
        }
    });
    

    Then at the top of your class you can use:

    UIManager.put("ComboBox.disabledForeground", Color.BLACK);
    

    to see the foreground color used by the disable combobox. When the combo box is disabled the editor is not displayed so normal rendering is done.

    So you don't need to use the JTextPane as the editor.