Reputation: 16146
I am creating a combobox that shows a tooltip when the display area is too small to show all text of the selected item.
The problem I have with the solution I have so far:
DefaultListCellRenderer.setBounds
to do that.The little MCVE I included at the bottom shows what I came up with. It shows four comboboxes. The first and third are regular untweaked JComboBox instances, the second and fourth are tweaked comboboxes to show tooltips if the display area is too small for the text. The first and second are enabled, the third and fourth disabled; this to better show the difference in look and feel. A snapshot:
How can I maintain the look and feel of a regular combobox and have a tooltip when the display area becomes too small? Am I on the right path and if so, what do I need to do further? If I am not, what should I do instead?
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ComboWithTooltip {
public static void main(String[] args) {
try {
// On my system: "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName( ));
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| UnsupportedLookAndFeelException e) {
System.err.println("Can't set look and feel");
return;
}
EventQueue.invokeLater (
new Runnable() {
@Override
public void run() {
buildFrame().setVisible(true);
}
}
);
}
static JFrame buildFrame() {
JPanel contentPane = new JPanel();
BoxLayout layout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
contentPane.setLayout(layout);
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo(true/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createTweakedCombo(true/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo(false/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createTweakedCombo(false/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
JFrame frame = new JFrame();
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
return frame;
}
private final static String[] items = new String[]{ "Long text 1234444444444444444444444", "Some more of that long texttttttttttttttttttttt", "The longer the text, the harder to read it becomessssssssssssssssssssssssssssssssssssssss" };
@SuppressWarnings("serial")
private static JComboBox<String> createDefaultCombo(boolean enabled) {
JComboBox<String> comboBoxDefault = new JComboBox<String>(items) {
@Override
public Dimension getPreferredSize() {
return new Dimension(100,25); // intentionally too small
}
};
comboBoxDefault.setEnabled(enabled);
return comboBoxDefault;
}
@SuppressWarnings("serial")
private static class JComboBoxTweaked extends JComboBox<String> {
public JComboBoxTweaked(String[] items) {
super(items);
setRenderer (
new DefaultListCellRenderer() {
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds( x, y, width, height );
if( width != 0 ) {
if( getPreferredSize( ).width > getSize( ).width )
JComboBoxTweaked.this.setToolTipText( getText( ) );
else
JComboBoxTweaked.this.setToolTipText( null );
}
}
}
);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(100,25); // intentionally too small
}
};
private static JComboBox<String> createTweakedCombo(boolean enabled) {
JComboBox<String> comboBoxTweaked = new JComboBoxTweaked(items);
comboBoxTweaked.setEnabled(enabled);
return comboBoxTweaked;
}
}
Upvotes: 2
Views: 498
Reputation: 9833
Another way is to use ListCellRenderer
which can be obtained by JComboBox#getRenderer()
method instead of new DefaultListCellRenderer() {...}
.
ListCellRenderer<? super String> renderer = getRenderer();
setRenderer(new ListCellRenderer<String>() {
@Override public Component getListCellRendererComponent(
JList<? extends String> list, String value, int index,
boolean isSelected, boolean cellHasFocus) {
Component c = renderer.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
In the case of WindowsLookAndFeel
, it will be WindowsComboBoxRenderer
:
// @see com/sun/java/swing/plaf/windows/WindowsComboBoxUI.java
/**
* Subclassed to set opacity {@code false} on the renderer
* and to show border for focused cells.
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
private static class WindowsComboBoxRenderer
extends BasicComboBoxRenderer.UIResource {
ComboWithTooltip2.java
import java.awt.*;
import java.util.Objects;
import javax.swing.*;
public class ComboWithTooltip2 {
public static void main(String[] args) {
try {
// On my system: "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName( ));
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| UnsupportedLookAndFeelException e) {
System.err.println("Can't set look and feel");
return;
}
EventQueue.invokeLater (
new Runnable() {
@Override
public void run() {
buildFrame().setVisible(true);
}
}
);
}
static JFrame buildFrame() {
JPanel contentPane = new JPanel();
BoxLayout layout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
contentPane.setLayout(layout);
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo(true/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createTweakedCombo(true/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo(false/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createTweakedCombo(false/*enabled*/));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo2(true));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo2(false));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(Box.createVerticalGlue());
contentPane.setPreferredSize(new Dimension(100, 320));
JFrame frame = new JFrame();
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
return frame;
}
private final static String[] items = new String[]{ "Long text 1234444444444444444444444", "Some more of that long texttttttttttttttttttttt", "The longer the text, the harder to read it becomessssssssssssssssssssssssssssssssssssssss" };
@SuppressWarnings("serial")
private static JComboBox<String> createDefaultCombo(boolean enabled) {
JComboBox<String> comboBoxDefault = new JComboBox<String>(items) {
@Override
public Dimension getPreferredSize() {
return new Dimension(100,25); // intentionally too small
}
};
comboBoxDefault.setEnabled(enabled);
return comboBoxDefault;
}
@SuppressWarnings("serial")
private static class JComboBoxTweaked extends JComboBox<String> {
public JComboBoxTweaked(String[] items) {
super(items);
setRenderer (
new DefaultListCellRenderer() {
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds( x, y, width, height );
if( width != 0 ) {
if( getPreferredSize( ).width > getSize( ).width )
JComboBoxTweaked.this.setToolTipText( getText( ) );
else
JComboBoxTweaked.this.setToolTipText( null );
}
}
}
);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(100,25); // intentionally too small
}
};
private static JComboBox<String> createTweakedCombo(boolean enabled) {
JComboBox<String> comboBoxTweaked = new JComboBoxTweaked(items);
comboBoxTweaked.setEnabled(enabled);
return comboBoxTweaked;
}
private static JComboBox<String> createDefaultCombo2(boolean enabled) {
JComboBox<String> comboBox = new JComboBox<String>(items) {
@Override public void updateUI() {
setRenderer(null);
super.updateUI();
ListCellRenderer<? super String> renderer = getRenderer();
setRenderer(new ListCellRenderer<String>() {
@Override public Component getListCellRendererComponent(JList<? extends String> list, String value, int index, boolean isSelected, boolean cellHasFocus) {
Component c = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
Insets i1 = ((JComponent) c).getInsets();
Insets i2 = getInsets();
int availableWidth = getWidth() - i1.top - i1.bottom - i2.top - i2.bottom;
if (index < 0) {
int buttonSize = getHeight() - i2.top - i2.bottom;
availableWidth -= buttonSize;
JTextField tf = (JTextField) getEditor().getEditorComponent();
Insets i3 = tf.getMargin();
availableWidth -= i3.left + i3.right;
}
String str = Objects.toString(value, "");
FontMetrics fm = c.getFontMetrics(getFont());
setToolTipText(fm.stringWidth(str) > availableWidth ? str : null);
return c;
}
});
}
};
comboBox.setEnabled(enabled);
return comboBox;
}
}
Upvotes: 1
Reputation: 11327
Solution here: provide delegation to the standard L&F renderer. Here is the code:
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.util.Objects;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ComboWithTooltip {
public static void main(String[] args) {
try {
// On my system: "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| UnsupportedLookAndFeelException e) {
System.err.println("Can't set look and feel");
return;
}
EventQueue.invokeLater(
new Runnable() {
@Override
public void run() {
buildFrame().setVisible(true);
}
});
}
static JFrame buildFrame() {
JPanel contentPane = new JPanel();
BoxLayout layout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
contentPane.setLayout(layout);
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo(true/* enabled */));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createTweakedCombo(true/* enabled */));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createDefaultCombo(false/* enabled */));
contentPane.add(Box.createVerticalStrut(25));
contentPane.add(createTweakedCombo(false/* enabled */));
contentPane.add(Box.createVerticalStrut(25));
JFrame frame = new JFrame();
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
return frame;
}
private final static String[] items =
new String[] {"Long text 1234444444444444444444444", "Some more of that long texttttttttttttttttttttt",
"The longer the text, the harder to read it becomessssssssssssssssssssssssssssssssssssssss"};
@SuppressWarnings("serial")
private static JComboBox<String> createDefaultCombo(boolean enabled) {
JComboBox<String> comboBoxDefault = new JComboBox<String>(items) {
@Override
public Dimension getPreferredSize() {
return new Dimension(100, 25); // intentionally too small
}
};
comboBoxDefault.setEnabled(enabled);
return comboBoxDefault;
}
@SuppressWarnings("serial")
private static class JComboBoxTweaked extends JComboBox<String> {
public JComboBoxTweaked(String[] items) {
super(items);
setRenderer(new DelegateComboBoxRenderer<>(getRenderer(), this));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(100, 25); // intentionally too small
}
};
private static class DelegateComboBoxRenderer<E> extends JPanel implements ListCellRenderer<E> {
private final ListCellRenderer<E> delegate;
private final JComboBox<?> combo;
private String lastText;
/**
* @param delegate
*/
public DelegateComboBoxRenderer(ListCellRenderer<E> delegate, JComboBox<?> combo) {
setLayout(new GridLayout(1, 1));
setOpaque(true);
this.delegate = delegate;
this.combo = combo;
}
@Override
public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected,
boolean cellHasFocus) {
removeAll();
Component c = delegate.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
add(c);
if (c instanceof JLabel) {
lastText = ((JLabel) c).getText();
} else {
lastText = Objects.toString(value, null);
}
setOpaque(c.isOpaque()); // some renderers works with opacity state, we need to consider it
return this;
}
// some UI classes reset background/foreground, so we need to forward them
@Override
public void setBackground(Color bg) {
super.setBackground(bg);
for (Component c : getComponents()) {
c.setBackground(bg);
}
}
@Override
public void setForeground(Color fg) {
super.setForeground(fg);
for (Component c : getComponents()) {
c.setForeground(fg);
}
}
// not sure whether it's required
@Override
public void setFont(Font font) {
super.setFont(font);
for (Component c : getComponents()) {
c.setFont(font);
}
}
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
if (width != 0) {
if (getPreferredSize().width > getSize().width) {
combo.setToolTipText(lastText);
} else {
combo.setToolTipText(null);
}
}
}
}
private static JComboBox<String> createTweakedCombo(boolean enabled) {
JComboBox<String> comboBoxTweaked = new JComboBoxTweaked(items);
comboBoxTweaked.setEnabled(enabled);
return comboBoxTweaked;
}
}
Upvotes: 2