子隐藏后触发父尺寸重新计算

问题描述 投票:0回答:1

我编写了一个自定义

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>

下拉菜单,首先单击 — 额外空间

下拉菜单,第二次单击 — 没有多余空间

java swing
1个回答
0
投票

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();
    }
© www.soinside.com 2019 - 2024. All rights reserved.