There’s a class called BoundsPopupMenuListener
, that can control the size of the popup that opens the combo options, according to the larger item size.
After some adaptations, I was able to make that, when changing an item in the combo, it was resized to the size of the item.
Just add the class BoundsPopupMenuListener
in your project and set it as a combo popup system, 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 sections are those with explanatory comments.
The result of this example would be like this:
The code of the class BoundsPopupMenuListener
is available at the link at the end of the reply, but I will copy it here to facilitate the query if the link becomes unavailable:
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
/**
* This class will change the bounds of the JComboBox popup menu to support
* different functionality. It will support the following features: - a
* horizontal scrollbar can be displayed when necessary - the popup can be wider
* than the combo box - the popup can be displayed above the combo box
*
* Class will only work for a JComboBox that uses a BasicComboPop.
*/
public class BoundsPopupMenuListener implements PopupMenuListener {
private boolean scrollBarRequired = true;
private boolean popupWider;
private int maximumWidth = -1;
private boolean popupAbove;
private JScrollPane scrollPane;
/**
* Convenience constructore to allow the display of a horizontal scrollbar
* when required.
*/
public BoundsPopupMenuListener() {
this(true, false, -1, false);
}
/**
* Convenience constructor that allows you to display the popup wider and/or
* above the combo box.
*
* @param popupWider when true, popup width is based on the popup preferred
* width
* @param popupAbove when true, popup is displayed above the combobox
*/
public BoundsPopupMenuListener(boolean popupWider, boolean popupAbove) {
this(true, popupWider, -1, popupAbove);
}
/**
* Convenience constructor that allows you to display the popup wider than
* the combo box and to specify the maximum width
*
* @param maximumWidth the maximum width of the popup. The popupAbove value
* is set to "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
Why not set a larger size? Resizing it dynamically would imply rebuilding the entire screen with each resize.
– user28595
Because the user can enter a very large "content", so if the content is too large, the combo will get huge.
– Gustavo Santos
But if you resize according to the size, it will get huge anyway.
– user28595
So, my question, is to know if there is a way not to modify this size, and when selecting the item, it shows only the full content, while the user is choosing, then it can continue of the size I set (small = 100/21).
– Gustavo Santos
Honestly your doubt is a little confused, what you explained now is different from what is in the question.
– user28595
Gustavo, with what he additionally presented my answer was out of what I wanted, so I withdrew.
– Leo
@Leo all right. I appreciate you trying to help me!
– Gustavo Santos
I found a solution that resizes the combo pop up, but the combo does not resize if the option is larger than it.
– user28595