There is a class called BoundsPopupMenuListener
, which can control the popup size that opens from the combo options, according to the larger item.
After some adjustments, I was able to resize an item in the combo by resizing it to the size of the item.
Just add the BoundsPopupMenuListener
class to your project and set it as a combo popup listener, as follows in the executable example below:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ComboResizing {
public void createAndShowGUI() {
final JFrame frame = new JFrame();
frame.setTitle("Frame Teste");
frame.setPreferredSize(new Dimension(300, 200));
frame.setLayout(new FlowLayout(FlowLayout.CENTER));
String[] tabs = {"txt short", "text more longer", "text more more longer"};
JComboBox combo = new JComboBox(tabs);
combo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//este método redefine o tamanho preferido
//do combo baseado num valor prototipo
//passado como parametro, neste caso
//o item selecionado
combo.setPrototypeDisplayValue(combo.getSelectedItem());
//força o frame a ser redesenhado
frame.pack();
}
});
//classe que define o tamanho do popup do combo
//conforme o tamanho do valor prototipo
//e redimensiona se houver itens muito grande
BoundsPopupMenuListener listener = new BoundsPopupMenuListener(true, false);
combo.addPopupMenuListener(listener);
combo.setPrototypeDisplayValue(combo.getSelectedItem());
frame.add(combo);
frame.pack();
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new ComboResizing().createAndShowGUI();
}
});
}
}
The relevant excerpts are those that have explanatory comments.
The result of this example would look like this:
ThecodeforclassBoundsPopupMenuListener
isavailableonthelinkattheendoftheanswer,butI'llcopyithereforeaseofreferenceifthelinkisunavailable:
importjava.awt.*;importjavax.swing.*;importjavax.swing.event.*;importjavax.swing.plaf.basic.*;/***ThisclasswillchangetheboundsoftheJComboBoxpopupmenutosupport*differentfunctionality.Itwillsupportthefollowingfeatures:-a*horizontalscrollbarcanbedisplayedwhennecessary-thepopupcanbewider*thanthecombobox-thepopupcanbedisplayedabovethecombobox**ClasswillonlyworkforaJComboBoxthatusesaBasicComboPop.*/publicclassBoundsPopupMenuListenerimplementsPopupMenuListener{privatebooleanscrollBarRequired=true;privatebooleanpopupWider;privateintmaximumWidth=-1;privatebooleanpopupAbove;privateJScrollPanescrollPane;/***Convenienceconstructoretoallowthedisplayofahorizontalscrollbar*whenrequired.*/publicBoundsPopupMenuListener(){this(true,false,-1,false);}/***Convenienceconstructorthatallowsyoutodisplaythepopupwiderand/or*abovethecombobox.**@parampopupWiderwhentrue,popupwidthisbasedonthepopuppreferred*width*@parampopupAbovewhentrue,popupisdisplayedabovethecombobox*/publicBoundsPopupMenuListener(booleanpopupWider,booleanpopupAbove){this(true,popupWider,-1,popupAbove);}/***Convenienceconstructorthatallowsyoutodisplaythepopupwiderthan*thecomboboxandtospecifythemaximumwidth**@parammaximumWidththemaximumwidthofthepopup.ThepopupAbovevalue*issetto"true".
*/
public BoundsPopupMenuListener(int maximumWidth) {
this(true, true, maximumWidth, false);
}
/**
* General purpose constructor to set all popup properties at once.
*
* @param scrollBarRequired display a horizontal scrollbar when the
* preferred width of popup is greater than width of scrollPane.
* @param popupWider display the popup at its preferred with
* @param maximumWidth limit the popup width to the value specified (minimum
* size will be the width of the combo box)
* @param popupAbove display the popup above the combo box
*
*/
public BoundsPopupMenuListener(
boolean scrollBarRequired, boolean popupWider, int maximumWidth, boolean popupAbove) {
setScrollBarRequired(scrollBarRequired);
setPopupWider(popupWider);
setMaximumWidth(maximumWidth);
setPopupAbove(popupAbove);
}
/**
* Return the maximum width of the popup.
*
* @return the maximumWidth value
*/
public int getMaximumWidth() {
return maximumWidth;
}
/**
* Set the maximum width for the popup. This value is only used when
* setPopupWider( true ) has been specified. A value of -1 indicates that
* there is no maximum.
*
* @param maximumWidth the maximum width of the popup
*/
public void setMaximumWidth(int maximumWidth) {
this.maximumWidth = maximumWidth;
}
/**
* Determine if the popup should be displayed above the combo box.
*
* @return the popupAbove value
*/
public boolean isPopupAbove() {
return popupAbove;
}
/**
* Change the location of the popup relative to the combo box.
*
* @param popupAbove true display popup above the combo box, false display
* popup below the combo box.
*/
public void setPopupAbove(boolean popupAbove) {
this.popupAbove = popupAbove;
}
/**
* Determine if the popup might be displayed wider than the combo box
*
* @return the popupWider value
*/
public boolean isPopupWider() {
return popupWider;
}
/**
* Change the width of the popup to be the greater of the width of the combo
* box or the preferred width of the popup. Normally the popup width is
* always the same size as the combo box width.
*
* @param popupWider true adjust the width as required.
*/
public void setPopupWider(boolean popupWider) {
this.popupWider = popupWider;
}
/**
* Determine if the horizontal scroll bar might be required for the popup
*
* @return the scrollBarRequired value
*/
public boolean isScrollBarRequired() {
return scrollBarRequired;
}
/**
* For some reason the default implementation of the popup removes the
* horizontal scrollBar from the popup scroll pane which can result in the
* truncation of the rendered items in the popop. Adding a scrollBar back to
* the scrollPane will allow horizontal scrolling if necessary.
*
* @param scrollBarRequired true add horizontal scrollBar to scrollPane
* false remove the horizontal scrollBar
*/
public void setScrollBarRequired(boolean scrollBarRequired) {
this.scrollBarRequired = scrollBarRequired;
}
/**
* Alter the bounds of the popup just before it is made visible.
*/
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
JComboBox comboBox = (JComboBox) e.getSource();
if (comboBox.getItemCount() == 0) {
return;
}
final Object child = comboBox.getAccessibleContext().getAccessibleChild(0);
if (child instanceof BasicComboPopup) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
customizePopup((BasicComboPopup) child);
}
});
}
}
protected void customizePopup(BasicComboPopup popup) {
scrollPane = getScrollPane(popup);
if (popupWider) {
popupWider(popup);
}
checkHorizontalScrollBar(popup);
// For some reason in JDK7 the popup will not display at its preferred
// width unless its location has been changed from its default
// (ie. for normal "pop down" shift the popup and reset)
Component comboBox = popup.getInvoker();
Point location = comboBox.getLocationOnScreen();
if (popupAbove) {
int height = popup.getPreferredSize().height;
popup.setLocation(location.x, location.y - height);
} else {
int height = comboBox.getPreferredSize().height;
popup.setLocation(location.x, location.y + height - 1);
popup.setLocation(location.x, location.y + height);
}
}
/*
* Adjust the width of the scrollpane used by the popup
*/
protected void popupWider(BasicComboPopup popup) {
JList list = popup.getList();
// Determine the maximimum width to use:
// a) determine the popup preferred width
// b) limit width to the maximum if specified
// c) ensure width is not less than the scroll pane width
int popupWidth = list.getPreferredSize().width
+ 5 // make sure horizontal scrollbar doesn't appear
+ getScrollBarWidth(popup, scrollPane);
if (maximumWidth != -1) {
popupWidth = Math.min(popupWidth, maximumWidth);
}
Dimension scrollPaneSize = scrollPane.getPreferredSize();
popupWidth = Math.max(popupWidth, scrollPaneSize.width);
// Adjust the width
scrollPaneSize.width = popupWidth;
scrollPane.setPreferredSize(scrollPaneSize);
scrollPane.setMaximumSize(scrollPaneSize);
}
/*
* This method is called every time:
* - to make sure the viewport is returned to its default position
* - to remove the horizontal scrollbar when it is not wanted
*/
private void checkHorizontalScrollBar(BasicComboPopup popup) {
// Reset the viewport to the left
JViewport viewport = scrollPane.getViewport();
Point p = viewport.getViewPosition();
p.x = 0;
viewport.setViewPosition(p);
// Remove the scrollbar so it is never painted
if (!scrollBarRequired) {
scrollPane.setHorizontalScrollBar(null);
return;
}
// Make sure a horizontal scrollbar exists in the scrollpane
JScrollBar horizontal = scrollPane.getHorizontalScrollBar();
if (horizontal == null) {
horizontal = new JScrollBar(JScrollBar.HORIZONTAL);
scrollPane.setHorizontalScrollBar(horizontal);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}
// Potentially increase height of scroll pane to display the scrollbar
if (horizontalScrollBarWillBeVisible(popup, scrollPane)) {
Dimension scrollPaneSize = scrollPane.getPreferredSize();
scrollPaneSize.height += horizontal.getPreferredSize().height;
scrollPane.setPreferredSize(scrollPaneSize);
scrollPane.setMaximumSize(scrollPaneSize);
scrollPane.revalidate();
}
}
/*
* Get the scroll pane used by the popup so its bounds can be adjusted
*/
protected JScrollPane getScrollPane(BasicComboPopup popup) {
JList list = popup.getList();
Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, list);
return (JScrollPane) c;
}
/*
* I can't find any property on the scrollBar to determine if it will be
* displayed or not so use brute force to determine this.
*/
protected int getScrollBarWidth(BasicComboPopup popup, JScrollPane scrollPane) {
int scrollBarWidth = 0;
JComboBox comboBox = (JComboBox) popup.getInvoker();
if (comboBox.getItemCount() > comboBox.getMaximumRowCount()) {
JScrollBar vertical = scrollPane.getVerticalScrollBar();
scrollBarWidth = vertical.getPreferredSize().width;
}
return scrollBarWidth;
}
/*
* I can't find any property on the scrollBar to determine if it will be
* displayed or not so use brute force to determine this.
*/
protected boolean horizontalScrollBarWillBeVisible(BasicComboPopup popup, JScrollPane scrollPane) {
JList list = popup.getList();
int scrollBarWidth = getScrollBarWidth(popup, scrollPane);
int popupWidth = list.getPreferredSize().width + scrollBarWidth;
return popupWidth > scrollPane.getPreferredSize().width;
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
// In its normal state the scrollpane does not have a scrollbar
if (scrollPane != null) {
scrollPane.setHorizontalScrollBar(null);
}
}
}
References:
Combo Box Popup
Why JComboBox ignore PrototypeDisplayValue