1

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

我知道如何实现这一点的唯一方法是创建 ComboBoxUI 的自定义实例并将其设置在 JComboBox 上(示例代码演示了目标:顶部组合框显示宽弹出窗口,底部是默认行为)。然而,由于它替换了 ComboBox 的 UI,它在某些 L&F 上可能看起来很奇怪(例如,对于 WinXP Luna 主题,ComboBox 看起来像 Classic 主题)。

有没有办法以与 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);
    }

}
4

2 回答 2

2
  1. 通过使用 GBC,您可以在容器中设置适当的高度和重量

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

  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();
            }
        });
    }
}
于 2012-07-11T15:15:46.727 回答
1

这个 hack 似乎适用于 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;
    }

}
于 2012-07-12T13:48:26.327 回答