Alter JComboBox popup size without disturbing Look and Feel?

别等时光非礼了梦想. 提交于 2019-12-20 03:54:09

问题


I'm looking for a way to alter the width of a JComboBox's popup. Basically, the popup should be as wide as the widest combobox entry requires, not as wide as the combobox currently is.

The only way I know how to achieve this is creating a custom instance of ComboBoxUI and set that on the JComboBox (Example code demonstrates the goal: Top Combobox shows wide popup, Bottom is the default behavior). However since this replaces the UI of the ComboBox, it may look odd on some L&F's (For example with WinXP Luna theme the ComboBox looks like Classic theme).

Is there a way to achieve this behavior in a L&F agnostic way?

public class CustomCombo extends JComboBox {

    final static class CustomComboUI extends BasicComboBoxUI {
        protected ComboPopup createPopup() {
            BasicComboPopup popup = new BasicComboPopup(comboBox) {
                @Override
                protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {
                    return super.computePopupBounds(px, py, Math.max(
                            comboBox.getPreferredSize().width, pw), ph);
                }
            };
            popup.getAccessibleContext().setAccessibleParent(comboBox);
            return popup;
        }
    }

    {
        setUI(new CustomComboUI());
    }

    public static void main(String[] argv) {
        try {
            final String className = UIManager.getSystemLookAndFeelClassName();
            UIManager.setLookAndFeel(className);
        } catch (final Exception e) {
            // ignore
        }
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createGUI();    
            }
        });
    }

    public static void createGUI() {
        JComboBox combo1 = new CustomCombo();
        JComboBox combo2 = new JComboBox();
        JPanel panel = new JPanel();
        JFrame frame = new JFrame("Testframe");
        combo1.addItem("1 Short item");
        combo1.addItem("2 A very long Item name that should display completely in the popup");
        combo1.addItem("3 Another short one");
        combo2.addItem("1 Short item");
        combo2.addItem("2 A very long Item name that should display completely in the popup");
        combo2.addItem("3 Another short one");
        panel.setPreferredSize(new Dimension(30, 50));
        panel.setLayout(new GridBagLayout());
        GridBagConstraints gc;
        gc = new GridBagConstraints(0, 0, 1, 1, 1D, 0D, GridBagConstraints.WEST,
                GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
        panel.add(combo1, gc);
        gc = new GridBagConstraints(0, 1, 1, 1, 1D, 0D, GridBagConstraints.WEST,
                GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
        panel.add(combo2, gc);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(panel, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }

}

回答1:


  1. by using GBC you are be able to set proper height and weight in the container

  2. as @trashgod mentioned next way is to set for PreferredSize by using JComboBox.setPrototypeDisplayValue()

  3. use Combo Box Popup by @camickr

  4. for derived JPopup must be used pack() otherwise too hard to change its Dimension easilly

.

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

public class ComboBoxExample extends JPanel implements ActionListener {
//http://stackoverflow.com/a/5058210/714968

    private static final long serialVersionUID = 1L;
    private JComboBox comboBox;

    public ComboBoxExample() {
        String[] petStrings = {"Select Pet", "Bird", "Cat", "Dog", "Rabbit", "Pig", "Other"};
        comboBox = new JComboBox(petStrings);
        comboBox.setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
        add(comboBox, BorderLayout.PAGE_START);
        JFrame frame = new JFrame("ComboBoxExample");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(comboBox);
        frame.setName("ComboBoxExample");
        frame.setLocation(150, 150);
        frame.pack();
        frame.setVisible(true);
        Timer timer = new javax.swing.Timer(2000, this);
        timer.start();
    }

    public void actionPerformed(ActionEvent e) {
        comboBox.showPopup();
        Object child = comboBox.getAccessibleContext().getAccessibleChild(0);
        BasicComboPopup popup = (BasicComboPopup) child;
        popup.setName("BasicComboPopup");
        JList list = popup.getList();
        Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, list);
        JScrollPane scrollPane = (JScrollPane) c;
        Dimension size = scrollPane.getSize();
        if (size.width > 30) {
            size.width -= 5;
        }
        scrollPane.setPreferredSize(size);
        scrollPane.setMaximumSize(size);
        Dimension popupSize = popup.getSize();
        popupSize.width = size.width;
        Component parent = popup.getParent();
        parent.setSize(popupSize);
        parent.validate();
        parent.repaint();
        Window mainFrame = SwingUtilities.windowForComponent(comboBox);
        //Returns the first Window ancestor of c, or null if c is not contained inside a Window.
        System.out.println(mainFrame.getName());
        Window popupWindow = SwingUtilities.windowForComponent(popup);
        System.out.println(popupWindow.getName());
        Window popupWindowa = SwingUtilities.windowForComponent(c);
        System.out.println(popupWindowa.getName());

        Window mainFrame1 = SwingUtilities.getWindowAncestor(comboBox);
        //Returns the first Window ancestor of c, or null if c is not contained inside a Window.
        System.out.println(mainFrame1.getName());
        Window popupWindow1 = SwingUtilities.getWindowAncestor(popup);
        System.out.println(popupWindow1.getName());

        Component mainFrame2 = SwingUtilities.getRoot(comboBox);
        //Returns the root component for the current component tree.
        System.out.println(mainFrame2.getName());
        Component popupWindow2 = SwingUtilities.getRoot(popup);
        System.out.println(popupWindow2.getName());
        //  For heavy weight popups you need always to pack() for the window
        if (popupWindow != mainFrame) {
            popupWindow.pack();
        }
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                ComboBoxExample comboBoxExample = new ComboBoxExample();
            }
        });
    }
}



回答2:


This hack seems to work with Java6/7. It basically overrides getSize() to detect if the method is called from within the popup UI. If it detects it is, it lies about the combobox's current size. The popup then determines its size according to faked size value.

The detection code is brutal (it looks at the call stack) and built around the assumption that the calling class' name contains ComboPopup. It may fail to detect the condition on some UI implementations, in that case it will simply retain the default behavior.

public class PopupHackComboBox extends JComboBox {

    // --------------------------------------------------------------
    // ---
    // --- Hack to get control of combobox popup size
    // ---
    // --------------------------------------------------------------
    /**
     * Gets the width the combo's popup should use.
     * 
     * Can be overwritten to return any width desired.
     */
    public int getPopupWidth() {
        final Dimension preferred = getPreferredSize();
        return Math.max(getWidth(), preferred.width);
    }

    @SuppressWarnings("deprecation")
    @Override
    public Dimension size() {
        return getSize((Dimension) null);
    }

    @Override
    public Dimension getSize() {
        return getSize((Dimension) null);
    }

    @Override
    public Dimension getSize(final Dimension dimension) {
        // If the method was called from the ComboPopup,
        // simply lie about the current size of the combo box.
        final int width = isCalledFromComboPopup() ? getPopupWidth() : getWidth();
        if (dimension == null) {
            return new Dimension(width, getHeight());
        }
        dimension.width = width;
        dimension.height = getHeight();
        return dimension;
    }

    /**
     * Hack method to determine if called from within the combo popup UI.
     */
    public boolean isCalledFromComboPopup() {
        try {
            final Throwable t = new Throwable();
            t.fillInStackTrace();
            StackTraceElement[] st = t.getStackTrace();
            // look only at top 5 elements of call stack
            int max = Math.min(st.length, 5);
            for (int i=0; i<max; ++i) {
                final String name = st[i].getClassName();
                if (name != null && name.contains("ComboPopup")) {
                    return true;
                }
            }
        } catch (final Exception e) {
            // if there was a problem, assume not called from combo popup
        }
        return false;
    }

}


来源:https://stackoverflow.com/questions/11435138/alter-jcombobox-popup-size-without-disturbing-look-and-feel

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!