How to "limit" a JComboBox according to the selected item?

6

I have a JComboBox , and I limit the size of it, with the following code:

jcb.setPreferredSize(new Dimension(100, 21));

But I wonder if there is a way that, after I choose an option, the content is completely displayed. What happens is that since I set a size smaller than the content, it hides whatever is larger (the content that is larger than the combo).

Follow the image to illustrate the situation:

An"illustration to help"

    
asked by anonymous 30.10.2016 / 00:19

1 answer

4

There is a class called BoundsPopupMenuListener , which can control the popup size that opens from the combo options, according to the larger item.

After some adjustments, I was able to resize an item in the combo by resizing it to the size of the item.

Just add the BoundsPopupMenuListener class to your project and set it as a combo popup listener, as follows in the executable example below:

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

public class ComboResizing {

    public void createAndShowGUI() {
        final JFrame frame = new JFrame();
        frame.setTitle("Frame Teste");
        frame.setPreferredSize(new Dimension(300, 200));
        frame.setLayout(new FlowLayout(FlowLayout.CENTER));

        String[] tabs = {"txt short", "text more longer", "text more more longer"};

        JComboBox combo = new JComboBox(tabs);

        combo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //este método redefine o tamanho preferido
                //do combo baseado num valor prototipo
                //passado como parametro, neste caso
                //o item selecionado
                combo.setPrototypeDisplayValue(combo.getSelectedItem());
                //força o frame a ser redesenhado               
                frame.pack();
            }
        });

        //classe que define o tamanho do popup do combo
        //conforme o tamanho do valor prototipo
        //e redimensiona se houver itens muito grande
        BoundsPopupMenuListener listener = new BoundsPopupMenuListener(true, false);
        combo.addPopupMenuListener(listener);
        combo.setPrototypeDisplayValue(combo.getSelectedItem());

        frame.add(combo);

        frame.pack();
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ComboResizing().createAndShowGUI();
            }
        });
    }
}

The relevant excerpts are those that have explanatory comments.

The result of this example would look like this:

ThecodeforclassBoundsPopupMenuListenerisavailableonthelinkattheendoftheanswer,butI'llcopyithereforeaseofreferenceifthelinkisunavailable:

importjava.awt.*;importjavax.swing.*;importjavax.swing.event.*;importjavax.swing.plaf.basic.*;/***ThisclasswillchangetheboundsoftheJComboBoxpopupmenutosupport*differentfunctionality.Itwillsupportthefollowingfeatures:-a*horizontalscrollbarcanbedisplayedwhennecessary-thepopupcanbewider*thanthecombobox-thepopupcanbedisplayedabovethecombobox**ClasswillonlyworkforaJComboBoxthatusesaBasicComboPop.*/publicclassBoundsPopupMenuListenerimplementsPopupMenuListener{privatebooleanscrollBarRequired=true;privatebooleanpopupWider;privateintmaximumWidth=-1;privatebooleanpopupAbove;privateJScrollPanescrollPane;/***Convenienceconstructoretoallowthedisplayofahorizontalscrollbar*whenrequired.*/publicBoundsPopupMenuListener(){this(true,false,-1,false);}/***Convenienceconstructorthatallowsyoutodisplaythepopupwiderand/or*abovethecombobox.**@parampopupWiderwhentrue,popupwidthisbasedonthepopuppreferred*width*@parampopupAbovewhentrue,popupisdisplayedabovethecombobox*/publicBoundsPopupMenuListener(booleanpopupWider,booleanpopupAbove){this(true,popupWider,-1,popupAbove);}/***Convenienceconstructorthatallowsyoutodisplaythepopupwiderthan*thecomboboxandtospecifythemaximumwidth**@parammaximumWidththemaximumwidthofthepopup.ThepopupAbovevalue*issetto"true".
     */
    public BoundsPopupMenuListener(int maximumWidth) {
        this(true, true, maximumWidth, false);
    }

    /**
     * General purpose constructor to set all popup properties at once.
     *
     * @param scrollBarRequired display a horizontal scrollbar when the
     * preferred width of popup is greater than width of scrollPane.
     * @param popupWider display the popup at its preferred with
     * @param maximumWidth limit the popup width to the value specified (minimum
     * size will be the width of the combo box)
     * @param popupAbove display the popup above the combo box
     *
     */
    public BoundsPopupMenuListener(
            boolean scrollBarRequired, boolean popupWider, int maximumWidth, boolean popupAbove) {
        setScrollBarRequired(scrollBarRequired);
        setPopupWider(popupWider);
        setMaximumWidth(maximumWidth);
        setPopupAbove(popupAbove);
    }

    /**
     * Return the maximum width of the popup.
     *
     * @return the maximumWidth value
     */
    public int getMaximumWidth() {
        return maximumWidth;
    }

    /**
     * Set the maximum width for the popup. This value is only used when
     * setPopupWider( true ) has been specified. A value of -1 indicates that
     * there is no maximum.
     *
     * @param maximumWidth the maximum width of the popup
     */
    public void setMaximumWidth(int maximumWidth) {
        this.maximumWidth = maximumWidth;
    }

    /**
     * Determine if the popup should be displayed above the combo box.
     *
     * @return the popupAbove value
     */
    public boolean isPopupAbove() {
        return popupAbove;
    }

    /**
     * Change the location of the popup relative to the combo box.
     *
     * @param popupAbove true display popup above the combo box, false display
     * popup below the combo box.
     */
    public void setPopupAbove(boolean popupAbove) {
        this.popupAbove = popupAbove;
    }

    /**
     * Determine if the popup might be displayed wider than the combo box
     *
     * @return the popupWider value
     */
    public boolean isPopupWider() {
        return popupWider;
    }

    /**
     * Change the width of the popup to be the greater of the width of the combo
     * box or the preferred width of the popup. Normally the popup width is
     * always the same size as the combo box width.
     *
     * @param popupWider true adjust the width as required.
     */
    public void setPopupWider(boolean popupWider) {
        this.popupWider = popupWider;
    }

    /**
     * Determine if the horizontal scroll bar might be required for the popup
     *
     * @return the scrollBarRequired value
     */
    public boolean isScrollBarRequired() {
        return scrollBarRequired;
    }

    /**
     * For some reason the default implementation of the popup removes the
     * horizontal scrollBar from the popup scroll pane which can result in the
     * truncation of the rendered items in the popop. Adding a scrollBar back to
     * the scrollPane will allow horizontal scrolling if necessary.
     *
     * @param scrollBarRequired true add horizontal scrollBar to scrollPane
     * false remove the horizontal scrollBar
     */
    public void setScrollBarRequired(boolean scrollBarRequired) {
        this.scrollBarRequired = scrollBarRequired;
    }

    /**
     * Alter the bounds of the popup just before it is made visible.
     */
    @Override
    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
        JComboBox comboBox = (JComboBox) e.getSource();

        if (comboBox.getItemCount() == 0) {
            return;
        }

        final Object child = comboBox.getAccessibleContext().getAccessibleChild(0);

        if (child instanceof BasicComboPopup) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    customizePopup((BasicComboPopup) child);
                }
            });
        }
    }

    protected void customizePopup(BasicComboPopup popup) {
        scrollPane = getScrollPane(popup);

        if (popupWider) {
            popupWider(popup);
        }

        checkHorizontalScrollBar(popup);

        //  For some reason in JDK7 the popup will not display at its preferred
        //  width unless its location has been changed from its default
        //  (ie. for normal "pop down" shift the popup and reset)
        Component comboBox = popup.getInvoker();
        Point location = comboBox.getLocationOnScreen();

        if (popupAbove) {
            int height = popup.getPreferredSize().height;
            popup.setLocation(location.x, location.y - height);
        } else {
            int height = comboBox.getPreferredSize().height;
            popup.setLocation(location.x, location.y + height - 1);
            popup.setLocation(location.x, location.y + height);
        }
    }

    /*
     *  Adjust the width of the scrollpane used by the popup
     */
    protected void popupWider(BasicComboPopup popup) {
        JList list = popup.getList();

        //  Determine the maximimum width to use:
        //  a) determine the popup preferred width
        //  b) limit width to the maximum if specified
        //  c) ensure width is not less than the scroll pane width
        int popupWidth = list.getPreferredSize().width
                + 5 // make sure horizontal scrollbar doesn't appear
                + getScrollBarWidth(popup, scrollPane);

        if (maximumWidth != -1) {
            popupWidth = Math.min(popupWidth, maximumWidth);
        }

        Dimension scrollPaneSize = scrollPane.getPreferredSize();
        popupWidth = Math.max(popupWidth, scrollPaneSize.width);

        //  Adjust the width
        scrollPaneSize.width = popupWidth;
        scrollPane.setPreferredSize(scrollPaneSize);
        scrollPane.setMaximumSize(scrollPaneSize);
    }

    /*
     *  This method is called every time:
     *  - to make sure the viewport is returned to its default position
     *  - to remove the horizontal scrollbar when it is not wanted
     */
    private void checkHorizontalScrollBar(BasicComboPopup popup) {
        //  Reset the viewport to the left

        JViewport viewport = scrollPane.getViewport();
        Point p = viewport.getViewPosition();
        p.x = 0;
        viewport.setViewPosition(p);

        //  Remove the scrollbar so it is never painted
        if (!scrollBarRequired) {
            scrollPane.setHorizontalScrollBar(null);
            return;
        }

        //  Make sure a horizontal scrollbar exists in the scrollpane
        JScrollBar horizontal = scrollPane.getHorizontalScrollBar();

        if (horizontal == null) {
            horizontal = new JScrollBar(JScrollBar.HORIZONTAL);
            scrollPane.setHorizontalScrollBar(horizontal);
            scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        }

        //  Potentially increase height of scroll pane to display the scrollbar
        if (horizontalScrollBarWillBeVisible(popup, scrollPane)) {
            Dimension scrollPaneSize = scrollPane.getPreferredSize();
            scrollPaneSize.height += horizontal.getPreferredSize().height;
            scrollPane.setPreferredSize(scrollPaneSize);
            scrollPane.setMaximumSize(scrollPaneSize);
            scrollPane.revalidate();
        }
    }

    /*
     *  Get the scroll pane used by the popup so its bounds can be adjusted
     */
    protected JScrollPane getScrollPane(BasicComboPopup popup) {
        JList list = popup.getList();
        Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, list);

        return (JScrollPane) c;
    }

    /*
     *  I can't find any property on the scrollBar to determine if it will be
     *  displayed or not so use brute force to determine this.
     */
    protected int getScrollBarWidth(BasicComboPopup popup, JScrollPane scrollPane) {
        int scrollBarWidth = 0;
        JComboBox comboBox = (JComboBox) popup.getInvoker();

        if (comboBox.getItemCount() > comboBox.getMaximumRowCount()) {
            JScrollBar vertical = scrollPane.getVerticalScrollBar();
            scrollBarWidth = vertical.getPreferredSize().width;
        }

        return scrollBarWidth;
    }

    /*
     *  I can't find any property on the scrollBar to determine if it will be
     *  displayed or not so use brute force to determine this.
     */
    protected boolean horizontalScrollBarWillBeVisible(BasicComboPopup popup, JScrollPane scrollPane) {
        JList list = popup.getList();
        int scrollBarWidth = getScrollBarWidth(popup, scrollPane);
        int popupWidth = list.getPreferredSize().width + scrollBarWidth;

        return popupWidth > scrollPane.getPreferredSize().width;
    }

    @Override
    public void popupMenuCanceled(PopupMenuEvent e) {
    }

    @Override
    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
        //  In its normal state the scrollpane does not have a scrollbar

        if (scrollPane != null) {
            scrollPane.setHorizontalScrollBar(null);
        }
    }
}

References:

Combo Box Popup

Why JComboBox ignore PrototypeDisplayValue

    
30.10.2016 / 01:06