【问题标题】:JComboBox preferred size with null value selected but not in ComboBoxModelJComboBox 首选大小,选择了空值但不在 ComboBoxModel 中
【发布时间】:2012-07-20 15:33:29
【问题描述】:

我有以下情况:在 JCombobox 中,首选大小基于最大项目大小。但是,此计算没有考虑为null 呈现的值。它只关心模型内部的值。因此,当用于呈现空值的文本大于其他元素时,标签会被截断,最后会出现三个点 (...)。我想避免这种情况。

这是我正在谈论的内容的一个小演示:

import java.awt.Component;
import java.awt.GridBagLayout;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class TestComboBox {

    protected void initUI() {
        JFrame frame = new JFrame(TestComboBox.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel(new GridBagLayout());
        JComboBox comboBox = new JComboBox(new Object[] { "Something", "Stuff", "Beep" });
        comboBox.setRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (value == null) {
                    setText("No selection");
                }
                return comp;
            }
        });
        comboBox.setSelectedItem(null);
        panel.add(comboBox);
        frame.add(panel);
        frame.setSize(200, 100);
        frame.setVisible(true);
    }

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

            @Override
            public void run() {
                new TestComboBox().initUI();
            }
        });
    }
}

我想知道您是否有任何建议。到目前为止,我的想法是扩展 JComboBox,覆盖首选大小,还执行空值渲染,并采用调用 super.preferredSize 的最大维度和空值渲染之一。但我觉得这有点令人失望。

我真的不想使用prototypeDisplayValue 绝对不是一个选项,因为我不知道该下拉列表中的值

【问题讨论】:

  • JComboBox.setPrototypeDisplayValue() 在需要强制屏幕大小的情况下,并查看Rob的代码示例
  • @mKorbel 对我来说,设置原型显示值与强制首选尺寸相同,我想避免这种情况。我不知道组合框中的值,并且在某些情况下,它可能会大于呈现的空值。我也不希望组合框通过强制使用首选尺寸来占用过多空间。我只希望我的 JComboBox 占用确切所需的空间来显示所有值,包括空值,尽管空值不在 ComboBoxModel 中。
  • I have no idea of the values that will be inside the combobox 这在已经可见的 GUI 中可能会适得其反
  • @mKorbel 感谢您的跟进。事实上,我们已经构建了一个完整的通用接口构建器,其中所有内容都绑定到实际的类和代码。您可以以图形方式完成所有这些操作。接口构建器会自动查找 Java 类、可用方法、兼容类型等……所以这意味着组合框不知道其中的值。在很晚的阶段,我可以发现它,但不是很早。此外,组合框可以绑定到动态模型,因此值可以多次更改。
  • aaach 这与容器大小相同,请设置自然大小,因为您有并且需要知道最大大小如何......,如果此代码仅来自一个原生操作系统

标签: java swing layout jcombobox


【解决方案1】:

我没有在代码中对此进行测试,但我的方法是:

  1. 确定渲染器返回的ComponentpreferredSizeJComboBox 的实际preferredSize 之间的差异。不是通过使用任何硬编码值,而是通过创建一个仅包含一个项目和一个已知渲染器的JComboBox 幕后,并将JComboBox 的首选大小与Component 的大小进行比较由已知渲染器返回。
  2. 每次通过将侦听器附加到 UIManager 来更改外观时重复步骤 1
  3. 覆盖实际JComboBoxgetPreferredSize并返回super.getPreferredSize()getPreferredSize( rendererComponent ) + calculatedDifference的最大宽度

这应该解决外观问题,避免不必要的计算,并且您可以轻松创建包含此功能的 JComboBox 扩展。

【讨论】:

  • +1 我喜欢所选择的方法,这使得它跨 L&F 稳健。我会让你知道结果
  • @GuillaumePolet 如果您发布了结果(可能还有结果),我们将不胜感激。想知道这是否真的有效
  • 实际上,当我尝试您的想法时,我发现有一种更简单的方法是创建一个具有单个值的 JComboBox:null 并直接获取其首选大小。然后在super.getPreferredSize() 和该组合框的首选大小之间进行联合,并将其作为首选大小返回。简单,无需破解,可跨所有平台和 L&F 工作。我更新了我的答案以表明这一点。
  • 仅供参考,我发布了我们讨论的替代解决方案,也不错。
【解决方案2】:

我将利用我们知道从DefaultListCellRenderer.getListCellRendererComponent 返回的ComponentDefaultListCellRenderer 对象本身的事实,并且它是JLabel 的一个实例。

我还假设您的外观以通常的方式计算组合框的首选大小,类似于BasicComboBoxUI

有了这些信息,这个解决方案可能很丑陋且效率低下,但它确实有效:

    comboBox.setRenderer(new DefaultListCellRenderer() {

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if (value == null) {
                setText("No selection");
            }
            return comp;
        }

      @Override
      public Dimension getPreferredSize() {
        // this doesn't work:
        // int minWidth = (new JLabel("No selection").getPreferredSize()).width;

        // this does work:
        String oldText = getText();
        setText("No selection");
        int minWidth = (super.getPreferredSize()).width;
        setText(oldText);


        Dimension d = super.getPreferredSize();
        if (d.width < minWidth) {
          return new Dimension(minWidth, d.height);
        } else {
          return d;
        }
      }

【讨论】:

  • 首先,感谢您花时间阅读问题并写下答案。虽然这种方法接近我的建议,但这还不够,也不是很健壮。不够:你没有考虑边框的大小,也没有考虑右边的箭头按钮,所以尺寸还是太小了。不稳健:在不同的外观和感觉中,在首选尺寸中还必须考虑其他一些因素。
  • @GuillaumePolet 您可以通过要求渲染器为null 值创建一个组件并测量该宽度来克服其中一个问题。但是,我同意边界,...(因为我打算发布或多或少相同的答案,但我遇到了同样的问题)
  • @Robin 是的,这就是我的想法。看看我发布的答案。我只是在寻找对我的代码的改进或任何更好的方法。欢迎提出建议和批评。谢谢。
  • @GuillaumePolet 实际上,我确实在两种外观和感觉中测试了我的答案,并且它在两种外观中都运行良好。但不知何故,我对物质和金属很幸运!我现在尝试了其他一些,但它不起作用。我确实考虑过为箭头按钮留出空间的问题,但在我看来BasicComboBoxUI.getMinimumSize() 已经为此添加了更正。它遍历所有渲染器,找到最大的尺寸,然后为按钮和装饰添加一些额外的空间。所以我需要做的就是正确计算渲染器本身的大小。
  • 我添加了我的答案的更新版本,该版本至少适用于 5 个 LAF。但是,最后,我可能会使用@GuillaumePolet 自己发布的版本。
【解决方案3】:

这就是我目前所得到的,但一个主要问题是交叉 L&F 问题。另一种方法是遍历 ComboBox 模型的所有值和“无选择”值,并检查哪个是最长的。然后我可以将它设置为prototypeDisplayValue。问题是我需要一个图形上下文来测量每个字符串的边界。

以下是我们通过@Enwired 和@Robin 找到的两个解决方案。感谢他们俩。

编辑:在与@Robin 讨论后,我发现这个解决方案实际上要简单得多,并且适用于所有平台和外观。唯一的缺点是我们需要创建一个额外的 JComboBox。

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagLayout;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;

public class TestComboBox {

    protected void initUI() {
        JFrame frame = new JFrame(TestComboBox.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel(new GridBagLayout());
        JComboBox comboBox = new JComboBox(new Object[] { "Something", "Stuff", "Beep" }) {

            private JComboBox internal;

            private JComboBox getInternalComboBox() {
                if (internal == null) {
                    internal = new JComboBox(new Object[] { null });
                }
                return internal;
            }

            @Override
            public Dimension getPreferredSize() {
                Dimension preferredSize = super.getPreferredSize();
                if (getSelectedItem() == null) {
                    getInternalComboBox().setRenderer(getRenderer());
                    Dimension nullDimension = getInternalComboBox().getPreferredSize();
                    preferredSize.width = Math.max(preferredSize.width, nullDimension.width);
                    preferredSize.height = Math.max(preferredSize.height, nullDimension.height);
                }
                return preferredSize;
            }

            @Override
            public void updateUI() {
                internal = null;
                super.updateUI();
            }
        };
        comboBox.setRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (value == null) {
                    setText("No selection");
                }
                return comp;
            }
        });
        comboBox.setSelectedItem(null);
        panel.add(comboBox);
        frame.add(panel);
        frame.setSize(200, 100);
        frame.setVisible(true);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            UnsupportedLookAndFeelException {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestComboBox().initUI();
            }
        });
    }
}

编辑 2:在与 @Enwired 讨论后,提出了这个替代解决方案,即直接覆盖 ListCellRenderer getPreferredSize。在这一个中,您可以尝试了解不同的可用 L&F。

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;

public class TestComboBox {

    protected void initUI() {
        final JFrame frame = new JFrame(TestComboBox.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel(new GridBagLayout());
        JComboBox comboBox = new JComboBox(new Object[] { "Something", "Stuff", "Beep" });
        comboBox.setRenderer(new DefaultListCellRenderer() {

            private Dimension nullDimesion;

            @Override
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                if (value != null && nullDimesion == null) {
                    nullDimesion = ((JComponent) getListCellRendererComponent(list, null, -1, false, false)).getPreferredSize();
                }
                Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (value == null) {
                    setText("No selection");
                }
                return comp;
            }

            @Override
            public Dimension getPreferredSize() {
                Dimension preferredSize = super.getPreferredSize();
                if (nullDimesion != null) {
                    preferredSize.width = Math.max(preferredSize.width, nullDimesion.width);
                    preferredSize.height = Math.max(preferredSize.height, nullDimesion.height);
                }
                return preferredSize;
            }

            @Override
            public void updateUI() {
                nullDimesion = null;
                super.updateUI();
            }
        });
        comboBox.setSelectedItem(null);
        final JComboBox uiComboBox = new JComboBox(UIManager.getInstalledLookAndFeels());
        uiComboBox.setRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (value instanceof LookAndFeelInfo) {
                    LookAndFeelInfo info = (LookAndFeelInfo) value;
                    setText(info.getName());
                }
                return comp;
            }
        });
        uiComboBox.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            UIManager.setLookAndFeel(((LookAndFeelInfo) uiComboBox.getSelectedItem()).getClassName());
                            SwingUtilities.updateComponentTreeUI(frame);
                        } catch (ClassNotFoundException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        } catch (InstantiationException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        } catch (IllegalAccessException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        } catch (UnsupportedLookAndFeelException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        }
                    }
                });
            }
        });
        panel.add(comboBox);
        panel.add(uiComboBox);
        frame.add(panel);
        frame.setSize(300, 100);
        frame.setVisible(true);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            UnsupportedLookAndFeelException {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestComboBox().initUI();
            }
        });
    }
}

【讨论】:

  • 随意评论或批评这种方法,这就是我要找的 ;-)
  • 我在答案中放置了一种可能的方法(评论太长了),但我必须强调我没有测试过这种方法,而且我当然不是改变 Swing 组件的专家。只是熟悉使用它们
  • 在第二个示例中,在更改 LAF 的 ActionListener 中,您需要将其包装在对 invokeLater() 的调用中。即使您已经在事件线程中,您也不希望 LAF 更改,直到组合框完成选择。 (我通过玩示例发现了这一点。使用第二个组合框上的向上/向下键会出现异常。)
  • @Enwired 好的,我会调查一下。确实很有可能应该从非嵌套事件中更新 UI。干杯。
猜你喜欢
  • 2011-08-02
  • 2021-04-16
  • 2018-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-07
相关资源
最近更新 更多