我编写了一个自定义
JComboBox
,一旦项目数量大到需要滚动条,就会显示搜索文本字段
它可以工作,但是有一个小问题,但到目前为止我无法修复
当您第一次单击第一个组合时,它的底部会包含一些额外的空间。因为垂直滚动条隐藏后它的尺寸还没有更新
当你再次点击它时,它的大小是正确的
似乎要摆脱这个问题,我需要在
getPreferredSize()
实现结束时以某种方式触发对 popupMenu
上的 componentHidden()
的另一个调用。然而,revalidate()
、repaint()
和doLayout()
都没有帮助
如何解决?
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>
pack()
有帮助
我没有想到
JPopupMenu
有这样的方法,因为它不是Window
子类型(我已经检查过)
@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());
popupMenu.pack();
searchTextField.requestFocusInWindow();
}