Triggering parent’s size recalculation after child hiding

I wrote a custom JComboBox that display a search text field once the number of items becomes big enough to necessitate a scroll bar

It works, but there’s a small but I can’t fix so far

When you click the first combo for the first time, it contains some extra space at the bottom. It’s because its size hasn’t yet been updated after the vertical scroll bar was hidden

When you click it again, its size is correct

It seems to get rid of that issue, I need to somehow trigger another call to getPreferredSize() on the popupMenu at the end of my componentHidden() implementation. However, neither revalidate(), repaint(), nor doLayout() helped

How do I fix it?

package demos.combo;

import di.ComboBoxes;

import javax.swing.*;

public class SearchableComboDemo {
    public static void main(String[] args) {
        JComboBox<String> firstCombo = ComboBoxes.searchableComboBox();
        JComboBox<String> secondCombo = ComboBoxes.searchableComboBox();
        firstCombo.setMaximumRowCount(3);
        secondCombo.setMaximumRowCount(3);

        firstCombo.addItem("Item A");
        firstCombo.addItem("Item B");
        firstCombo.addItem("Item C");

        secondCombo.addItem("Item D");
        secondCombo.addItem("Item E");
        secondCombo.addItem("Item F");
        secondCombo.addItem("Item G");

        JFrame frame = new JFrame();
        JPanel mainPanel = new JPanel();
        mainPanel.add(firstCombo);
        mainPanel.add(secondCombo);
        frame.setContentPane(mainPanel);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setVisible(true);
    }
}
package di;

import demos.combo.SearchableComboPopup;

import javax.swing.*;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;

public class ComboBoxes {
    private ComboBoxes() {
    }

    public static <T> JComboBox<T> searchableComboBox() {
        return searchableComboBox(new ArrayList<>());
    }

    public static <T> JComboBox<T> searchableComboBox(Collection<T> items) {
        ComboBoxModel<T> comboModel = new DefaultComboBoxModel<>(new Vector<>(items));
        JComboBox<T> comboBox = new JComboBox<>(comboModel);
        SearchableComboPopup<T> comboPopup = searchableComboPopup(comboBox);
        comboBox.setUI(searchableComboBoxUi(comboPopup));
        return comboBox;
    }

    private static <T> SearchableComboPopup<T> searchableComboPopup(JComboBox<T> comboBox) {
        return new SearchableComboPopup<T>(comboBox);
    }

    private static <T> ComboBoxUI searchableComboBoxUi(SearchableComboPopup<T> comboPopup) {
        return new BasicComboBoxUI() {
            @Override
            protected ComboPopup createPopup() {
                return comboPopup;
            }
        };
    }
}
package demos.combo;

import di.Actions;
import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.basic.ComboPopup;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;

public class SearchableComboPopup<T> implements ComboPopup {
    private final JComboBox<T> comboBox;
    private final JPopupMenu popupMenu;
    private final JTextField searchTextField;
    private final Box searchBox;
    private final JList<T> itemList;
    private final JScrollPane scrollPane;

    public SearchableComboPopup(JComboBox<T> comboBox) {
        this.comboBox = comboBox;
        this.searchTextField = createSearchTextField();
        this.searchBox = createSearchBox();
        this.itemList = createList(comboBox.getModel());
        this.scrollPane = createScrollPane();
        this.popupMenu = createPopupMenu();
    }

    private JTextField createSearchTextField() {
        JTextField textField = new JTextField();
        textField.addKeyListener(createSearchFieldKeyListener());
        textField.getDocument().addDocumentListener(createSearchTextFieldDocumentListener());
        return textField;
    }

    private KeyListener createSearchFieldKeyListener() {
        return new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                    ensureSelection();
                    itemList.requestFocusInWindow();
                }
            }

            private void ensureSelection() {
                if (itemList.isSelectionEmpty())
                    itemList.setSelectedIndex(0);
            }
        };
    }

    private DocumentListener createSearchTextFieldDocumentListener() {
        return new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                updateModel();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                updateModel();
            }

            private void updateModel() {
                String enteredText = searchTextField.getText();
                ArrayList<T> matchingElements = findMatchingElements(enteredText);
                DefaultListModel<T> modelWithOnlyMatchingElements = new DefaultListModel<>();
                matchingElements.forEach(modelWithOnlyMatchingElements::addElement);
                itemList.setModel(modelWithOnlyMatchingElements);
            }

            private ArrayList<T> findMatchingElements(String enteredText) {
                ArrayList<T> matchingElements = new ArrayList<>();
                ComboBoxModel<T> comboModel = comboBox.getModel();
                for (int i = 0; i < comboModel.getSize(); i++) {
                    T modelElement = comboModel.getElementAt(i);
                    if (StringUtils.containsIgnoreCase(modelElement.toString(), enteredText))
                        matchingElements.add(modelElement);
                }
                return matchingElements;
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
            }
        };
    }

    private Box createSearchBox() {
        Box searchBox = new Box(BoxLayout.Y_AXIS);
        searchBox.add(searchTextField);
        searchBox.add(Box.createRigidArea(new Dimension(0, 3)));
        return searchBox;
    }

    private JList<T> createList(ListModel<T> searchListModel) {
        JList<T> list = new JList<>(searchListModel);
        list.addMouseListener(createListMouseListener());
        list.addKeyListener(createListKeyListener());
        configureInputMap(list);
        return list;
    }

    private MouseListener createListMouseListener() {
        return new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                selectAndHide();
            }
        };
    }

    private void selectAndHide() {
        T selectedValue = itemList.getSelectedValue();
        if (selectedValue == null) return;
        comboBox.setSelectedItem(selectedValue);
        hide();
    }

    private KeyListener createListKeyListener() {
        return new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switchFocusToTextFieldIfNecessary(e);
                submitSelectionIfNecessary(e);
            }

            private void switchFocusToTextFieldIfNecessary(KeyEvent e) {
                boolean isUpmostItemSelected = itemList.getSelectedIndex() == 0;
                if (e.getKeyCode() == KeyEvent.VK_UP && isUpmostItemSelected)
                    searchTextField.requestFocusInWindow();
            }

            private void submitSelectionIfNecessary(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER)
                    selectAndHide();
            }
        };
    }

    private void configureInputMap(JList<T> list) {
        String switchFocusToTextFieldKey = "switchFocusToTextField";
        list.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_DOWN_MASK), switchFocusToTextFieldKey);
        list.getActionMap().put(switchFocusToTextFieldKey, Actions.requestFocusInWindow(searchTextField));
    }

    private JScrollPane createScrollPane() {
        JScrollPane scrollPane = new JScrollPane(itemList);
        return scrollPane;
    }

    private JPopupMenu createPopupMenu() {
        JPopupMenu popupMenu = new ComboBoxPopupMenu<>(comboBox);
        popupMenu.add(searchBox);
        popupMenu.add(scrollPane);
        return popupMenu;
    }

    @Override
    public void show() {
        comboBox.firePopupMenuWillBecomeVisible();
        int itemListVisibleRowCount = Math.min(comboBox.getItemCount(), comboBox.getMaximumRowCount());
        itemList.setVisibleRowCount(itemListVisibleRowCount);
        popupMenu.show(comboBox, 0, comboBox.getHeight());
        searchBox.setVisible(scrollPane.getVerticalScrollBar().isVisible());
        searchTextField.requestFocusInWindow();
    }

    @Override
    public void hide() {
        comboBox.firePopupMenuWillBecomeInvisible();
        searchTextField.setText("");
        popupMenu.setVisible(false);
        comboBox.firePopupMenuCanceled();
    }

    public void toggle() {
        if (isVisible()) hide();
        else show();
    }

    @Override
    public boolean isVisible() {
        return popupMenu.isVisible();
    }

    @Override
    @SuppressWarnings("unchecked")
    public JList<Object> getList() {
        return (JList<Object>) itemList;
    }

    @Override
    public MouseListener getMouseListener() {
        return createMouseListener();
    }

    private MouseListener createMouseListener() {
        return new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                toggle();
            }
        };
    }

    @Override
    public MouseMotionListener getMouseMotionListener() {
        return null;
    }

    @Override
    public KeyListener getKeyListener() {
        return null;
    }

    @Override
    public void uninstallingUI() {
    }

    private static class ComboBoxPopupMenu<T> extends JPopupMenu {
        private final JComboBox<T> comboBox;

        public ComboBoxPopupMenu(JComboBox<T> comboBox) {
            this.comboBox = comboBox;
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension preferredSize = super.getPreferredSize();
            preferredSize.width = comboBox.getWidth();
            return preferredSize;
        }
    }
}
package di;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class Actions {
    private Actions() {
    }

    public static Action requestFocusInWindow(Component componentToFocus) {
        return new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                componentToFocus.requestFocusInWindow();
            }
        };
    }
}
<!-- add it to your pom.xml, assuming you use Maven too -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.14.0</version>
        </dependency>

dropdown menu, first click — extra space

dropdown menu, second click — no extra space

New contributor

Wacage is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật