在不干扰外观的情况下更改JComboBox弹出窗口的大小



我正在寻找一种改变JComboBox弹出窗口宽度的方法。基本上,弹出窗口应该像最宽的组合框条目所要求的那样宽,而不是像当前的组合框那样宽

我知道如何实现这一点的唯一方法是创建一个自定义的ComboBoxUI实例,并在JComboBox上设置它(示例代码演示了目标:顶部组合框显示宽弹出窗口,底部是默认行为)。然而,由于这取代了组合框的UI,它在一些L&F(例如,使用WinXP Luna主题时,ComboBox看起来像经典主题)。

有没有一种方法可以在L&F不可知论的方式?

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. 通过使用GBC,您可以在集装箱中设置适当的高度和重量

  2. 正如@trashgod提到的,下一种方法是使用JComboBox.setPrototypeDisplayValue() 设置PreferredSize

  3. 使用@camickr 弹出的组合框

  4. 对于导出的JPopup,必须使用pack(),否则太难轻易更改其Dimension

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();
            }
        });
    }
}

此破解似乎适用于Java6/7。它基本上覆盖getSize()来检测是否从弹出UI中调用该方法。如果它检测到它是,它就谎报了组合框的当前大小。弹出窗口然后根据伪造的大小值确定其大小。

检测代码是残酷的(它查看调用堆栈),并基于调用类名包含ComboPopup的假设构建。它可能无法检测到某些UI实现上的条件,在这种情况下,它只会保留默认行为。

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;
    }
}

最新更新