改变JComboBox弹出尺寸而不打扰外观?

时间:2012-07-11 14:40:04

标签: java swing jcombobox

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

我知道如何实现这一目标的唯一方法是创建一个ComboBoxUI的自定义实例并在JComboBox上设置它(示例代码演示了目标:Top Combobox显示宽弹出,Bottom是默认行为)。然而,由于这取代了ComboBox的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);
    }

}

2 个答案:

答案 0 :(得分:2)

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

  2. 正如@trashgod提到的下一种方法是使用PreferredSize

  3. 设置JComboBox.setPrototypeDisplayValue()
  4. 使用@camick的Combo Box Popup

  5. 必须使用派生JPopup pack(),否则很难更改其Dimension easilly

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

答案 1 :(得分: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;
    }

}