JComboBox preferred size with zero value but not in ComboBoxModel

I have the following situation: in JCombobox, the preferred size is based on the largest element size. However, this calculation does not take into account the value displayed for null . It only cares about the values โ€‹โ€‹inside the model. Therefore, when the text for rendering a null value is larger than the other element, the label is truncated and I have three dots (...) at the end. I would like to avoid this situation.

Here is a small demonstration of what I'm talking about:

Truncated null value rendered

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

I was wondering if you have any suggestions. So far, my idea has been to extend the JComboBox, override the preferred size, also render for the null value and take the largest call size super.preferredSize and one of the null value render. But I find it a little disappointing.

I would prefer not to use prototypeDisplayValue , definitely not an option, since I have no idea about the values โ€‹โ€‹that will be in this dropdown menu .

+4
source share
3 answers

I did not test this in code, but my approach was as follows:

  • Define the difference between the preferredSize Component returned by the renderer and the actual preferredSize for the JComboBox . Not using hard-coded values, but simply creating a JComboBox behind a frame that contains only one element and a known renderer, and comparing the preferred JComboBox size with the Component size returned by the renowned renderer.
  • Repeat step 1 each time the Look-and-Feel changes, attaching the listener to the UIManager
  • getPreferredSize actual JComboBox and return the maximum width super.getPreferredSize() and getPreferredSize( rendererComponent ) + calculatedDifference

This should take care of problems with Look-and-Feel, avoid unnecessary calculations, and you can easily create a JComboBox extension that includes this functionality.

+1
source

I will take advantage of the fact that we know that the Component returned from DefaultListCellRenderer.getListCellRendererComponent is the DefaultListCellRenderer object DefaultListCellRenderer and that it is an instance of JLabel .

I also assume that your appearance calculates the preferred combo box size in the usual way, similar to BasicComboBoxUI .

With this information, this solution may be ugly and inefficient, but it works:

  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; } } 
+1
source

So, here is what I got so far, but one major problem is the problems with cross L & F. An alternative would be to iterate over all the values โ€‹โ€‹of the ComboBox model and the value "No choice" and check which one is the longest. Then I could set it as prototypeDisplayValue. The problem is that I need a graphical context to measure the boundaries of each line.

Here are two solutions we found out with @Enwired and @Robin. Thanks to both of them.

EDIT: after discussing with @Robin, I found out that this solution is actually much simpler and works on all platforms and looks and feels. The only drawback is that we need to create an additional 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(); } }); } } 

EDIT 2: after discussion with @Enwired, this alternative solution came about to directly override ListCellRenderer getPreferredSize. In this case, you can try to go through the various available L & Fs.

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

All Articles