简体   繁体   中英

Swing - Remove selected border and change arrow color of JComboBox

I'm trying to remove the selected border of a JComboBox (top arrow) and change the arrow color (bottom arrow)

If possible, how do I remove the outer border? (the darker gray one)

How do I go about doing this?

So you can do this by implementing a ComboBoxUI , or, actually, subclassing BasicComboBoxUI for example. The second option is better, because you only need to tweak some code in some places and you are ready (instead of implementing your own ComboBoxUI from scratch). So follows the code which does what you requested (hopefully):

import java.awt.Color;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.plaf.basic.BasicArrowButton;
import javax.swing.plaf.basic.BasicComboBoxUI;

public class MainComboBoxUI {
    public static class MyComboBoxUI extends BasicComboBoxUI {
        @Override
        protected void installDefaults() {
            super.installDefaults();
            LookAndFeel.uninstallBorder(comboBox); //Uninstalls the LAF border for both button and label of combo box.
        }

        @Override
        protected JButton createArrowButton() {
            //Feel free to play with the colors:
            final Color background = Color.CYAN.darker();     //Default is UIManager.getColor("ComboBox.buttonBackground").
            final Color pressedButtonBorderColor = Color.RED; //Default is UIManager.getColor("ComboBox.buttonShadow"). The color of the border of the button, while it is pressed.
            final Color triangle = Color.BLACK;               //Default is UIManager.getColor("ComboBox.buttonDarkShadow"). The color of the triangle.
            final Color highlight = background;               //Default is UIManager.getColor("ComboBox.buttonHighlight"). Another color to show the button as highlighted.
            final JButton button = new BasicArrowButton(BasicArrowButton.SOUTH, background, pressedButtonBorderColor, triangle, highlight);
            button.setName("ComboBox.arrowButton"); //Mandatory, as per BasicComboBoxUI#createArrowButton().
            return button;
        }
    }

    public static void main(final String[] args) {
        final JComboBox<String> combo = new JComboBox<>(new String[]{"A string", "B string 2", "C string 3"});
        combo.setUI(new MyComboBoxUI());

        final JPanel panel = new JPanel(new GridBagLayout());
        panel.add(combo);
        panel.setBackground(Color.RED.darker());

        final JFrame frame = new JFrame("MainComboBoxUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

First of, we remove the border from the combo box inside installDefaults() method. That will remove the border from both the button and the label. Then we mimic the createArrowButton() method of BasicComboBoxUI to create our own custom button. The default implementation of BasicComboBoxUI.createArrowButton() creates a BasicArrowButton with some construction-time colors. Those colors change the button's triangle's color for example and the background color.

That is for creating a BasicArrowButton . Although, if you want better control over the button (for example instead of a triangle you need an icon and/or some text on the button) then you can simply create a plain JButton inside the createArrowButton() instead of a BasicArrowButton . Then you can initialize it inside the same method, or, preferably modify it inside the configureArrowButton() . Like the following code does:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.plaf.basic.BasicComboBoxUI;

public class MainComboBoxUI2 {
    public static class MyComboBoxUI extends BasicComboBoxUI {
        @Override
        protected void installDefaults() {
            super.installDefaults();
            LookAndFeel.uninstallBorder(comboBox);
        }

        @Override
        protected JButton createArrowButton() {
            final JButton button = new JButton("V");
            button.setName("ComboBox.arrowButton"); //Mandatory, as per BasicComboBoxUI#createArrowButton().
            return button;
        }

        @Override
        public void configureArrowButton() {
            super.configureArrowButton(); //Do not forget this!
            arrowButton.setBackground(Color.CYAN.darker());
            arrowButton.setForeground(Color.BLUE);
        }
    }

    public static void main(final String[] args) {
        final JComboBox<String> combo = new JComboBox<>(new String[]{"A string", "B string 2", "C string 3"});
        combo.setUI(new MyComboBoxUI());
        combo.setPreferredSize(new Dimension(150, 45)); //Needed to be able to show the button's text.

        final JPanel panel = new JPanel(new GridBagLayout());
        panel.add(combo);
        panel.setBackground(Color.RED.darker());

        final JFrame frame = new JFrame("MainComboBoxUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

The only problem with the custom plain JButton approach (ie the second approach) is that the LayoutManager of the JComboBox will not take into account the preferred size of the arrow button if a protected flag inside the UI (named squareButton ) is true . If that flag is true , then the button's width is set equal to the button's height, which in turn is set equal to the combo box height. That's why in the second approach, I set the preferred size of the combo box manually to give it a height of 45 pixels which is enough to be able to display the text inside the button (in that specific case, where the text is equal to "V").

You can set the value of this flag for a single UI instance manually by overriding installDefaults() method in the BasicComboBoxUI and setting it to false for example. Otherwise, you can set its value for all combo boxes via calling UIManager.put("ComboBox.squareButton", Boolean.FALSE); in your code early enough (ie before the instantiation of each combo box). But as it turns out, because I tested it, when setting this value to false the size of the button overlaps the display area's size (the display area is where the selected value of the combo box is visible).

After some more digging, I found out that the cause of this problem is that the UI's getMinimumSize(...) method does not take into account the button's insets... That means the default border of the JButton around the text/icon is not accounted for. So after setting the squareButton flag to false , you will also need to override this method.

Putting them all together, you get the following final approach/code:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicComboBoxUI;

public class MainComboBoxUI3 {
    public static class MyComboBoxUI extends BasicComboBoxUI {
        @Override
        protected void installDefaults() {
            super.installDefaults();
            LookAndFeel.uninstallBorder(comboBox);
        }

        @Override
        protected JButton createArrowButton() {
            final JButton button = new JButton("Drop");
            button.setName("ComboBox.arrowButton"); //Mandatory, as per BasicComboBoxUI#createArrowButton().
            return button;
        }

        @Override
        public void configureArrowButton() {
            super.configureArrowButton(); //Do not forget this!
            arrowButton.setBackground(Color.CYAN.darker());
            arrowButton.setForeground(Color.BLUE);
            //arrowButton.setBorder(javax.swing.BorderFactory.createEmptyBorder());
        }

        //Overrided getMinimumSize to take into account the button's insets for both width and height:
        @Override
        public Dimension getMinimumSize(final JComponent c) {
            final Dimension mindim = super.getMinimumSize(c);
            final Insets buttonInsets = arrowButton.getInsets();
            return new Dimension(mindim.width + buttonInsets.left + buttonInsets.right, mindim.height + buttonInsets.top + buttonInsets.bottom);
        }
    }

    public static void main(final String[] args) {
        UIManager.put("ComboBox.squareButton", Boolean.FALSE); //Set all the combo boxes' button to non-square...
        final JComboBox<String> combo = new JComboBox<>(new String[]{"A string", "B string 2", "C string 3"});
        combo.setUI(new MyComboBoxUI());

        final JPanel panel = new JPanel(new GridBagLayout());
        panel.add(combo);
        panel.setBackground(Color.RED.darker());

        final JFrame frame = new JFrame("MainComboBoxUI");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

And the result looks like this:

组合框最终进场结果

Finally, if you want smaller border for the drop down button (ie the one with text "Drop" in the above picture), you can set the border of the arrowButton to (for example) javax.swing.BorderFactory.createEmptyBorder() inside the configureArrowButton() method...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM