当前位置: 首页 > 知识库问答 >
问题:

用jtextfield和jpopupmenu实现自动完成

湛鸿雪
2023-03-14

我想实现一个自动完成功能。目前我有一个包含JTextField的JPanel,当用户开始键入时,会出现一个包含几个选项的autocomplete(JPopupMenu)。

问题是它占用了文本字段的焦点,用户无法再键入。当我将焦点返回到文本字段时,用户不再有选项之间的导航(使用向上和向下按钮)。此外,对菜单的焦点不允许我截取它的KeyListener(不知道为什么),并且当我尝试处理文本字段端的输入时,我在尝试选择菜单项时遇到了问题。

所以我想要:

    null

是否可以处理菜单上的键盘事件并将键入事件转发回文本字段?

处理我的问题的正确方法是什么?

下面是代码。提前道谢!

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;


class TagVisual extends JPanel {

    private JTextField editField;

    public TagVisual() {

        FlowLayout layout = new FlowLayout();
        layout.setHgap(0);
        layout.setVgap(0);
        setLayout(layout);

        editField = new JTextField();
        editField.setBackground(Color.RED);

        editField.setPreferredSize(new Dimension(200, 20));

        editField.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {
                JPopupMenu menu = new JPopupMenu();
                menu.add("Item 1");
                menu.add("Item 2");
                menu.add("Item 3");
                menu.addKeyListener(new KeyListener() {
                    @Override
                    public void keyTyped(KeyEvent e) {
                        JOptionPane.showMessageDialog(TagVisual.this, "keyTyped");
                    }

                    @Override
                    public void keyPressed(KeyEvent e) {
                        JOptionPane.showMessageDialog(TagVisual.this, "keyPressed");
                    }

                    @Override
                    public void keyReleased(KeyEvent e) {
                        JOptionPane.showMessageDialog(TagVisual.this, "keyReleased");
                    }
                });
                menu.show(editField, 0, getHeight());
            }

            @Override
            public void keyPressed(KeyEvent e) {

            }

            @Override
            public void keyReleased(KeyEvent e) {

            }
        });

        add(editField, FlowLayout.LEFT);
    }

    public void place(JPanel panel) {
        panel.add(this);

        editField.grabFocus();
    }
}

public class MainWindow {

    private JPanel mainPanel;
    private JFrame frame;

    public MainWindow(JFrame frame) {

        mainPanel = new JPanel(new FlowLayout());
        TagVisual v = new TagVisual();
        v.place(mainPanel);

        this.frame = frame;
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("TextFieldPopupIssue");

        frame.setContentPane(new MainWindow(frame).mainPanel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
}

共有1个答案

魏熠彤
2023-03-14

我个人建议使用弹出窗口或自定义的JWindows而不是JPopupMenu,因为后者最初只用于显示菜单项。对于其他事情来说,它通常是有效的,但以不同的方式使用它并不是最佳实践。

例如,在示例中有几个菜单项作为自动完成选项--如果只有几个结果,这就可以很好地工作。但如果会有10个呢?要是50呢?还是500?您将不得不为这些情况创建额外的变通方法--要么将项目放入滚动窗格(哦,天哪,那看起来会很难看),要么将结果削减到几个最佳(这也不是最佳选项)。

因此,我使用JWindows作为AutoCompleteField的弹出窗口创建了一个小示例。它非常简单,但做了一些基本的事情,您希望从它和您提到的:

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Mikle Garin
 * @see https://stackoverflow.com/questions/45439231/implementing-autocomplete-with-jtextfield-and-jpopupmenu
 */

public final class AutocompleteField extends JTextField implements FocusListener, DocumentListener, KeyListener
{
    /**
     * {@link Function} for text lookup.
     * It simply returns {@link List} of {@link String} for the text we are looking results for.
     */
    private final Function<String, List<String>> lookup;

    /**
     * {@link List} of lookup results.
     * It is cached to optimize performance for more complex lookups.
     */
    private final List<String> results;

    /**
     * {@link JWindow} used to display offered options.
     */
    private final JWindow popup;

    /**
     * Lookup results {@link JList}.
     */
    private final JList list;

    /**
     * {@link #list} model.
     */
    private final ListModel model;

    /**
     * Constructs {@link AutocompleteField}.
     *
     * @param lookup {@link Function} for text lookup
     */
    public AutocompleteField ( final Function<String, List<String>> lookup )
    {
        super ();
        this.lookup = lookup;
        this.results = new ArrayList<> ();

        final Window parent = SwingUtilities.getWindowAncestor ( this );
        popup = new JWindow ( parent );
        popup.setType ( Window.Type.POPUP );
        popup.setFocusableWindowState ( false );
        popup.setAlwaysOnTop ( true );

        model = new ListModel ();
        list = new JList ( model );

        popup.add ( new JScrollPane ( list )
        {
            @Override
            public Dimension getPreferredSize ()
            {
                final Dimension ps = super.getPreferredSize ();
                ps.width = AutocompleteField.this.getWidth ();
                return ps;
            }
        } );

        addFocusListener ( this );
        getDocument ().addDocumentListener ( this );
        addKeyListener ( this );
    }

    /**
     * Displays autocomplete popup at the correct location.
     */
    private void showAutocompletePopup ()
    {
        final Point los = AutocompleteField.this.getLocationOnScreen ();
        popup.setLocation ( los.x, los.y + getHeight () );
        popup.setVisible ( true );
    }

    /**
     * Closes autocomplete popup.
     */
    private void hideAutocompletePopup ()
    {
        popup.setVisible ( false );
    }

    @Override
    public void focusGained ( final FocusEvent e )
    {
        SwingUtilities.invokeLater ( () -> {
            if ( results.size () > 0 )
            {
                showAutocompletePopup ();
            }
        } );
    }

    private void documentChanged ()
    {
        SwingUtilities.invokeLater ( () -> {
            // Updating results list
            results.clear ();
            results.addAll ( lookup.apply ( getText () ) );

            // Updating list view
            model.updateView ();
            list.setVisibleRowCount ( Math.min ( results.size (), 10 ) );

            // Selecting first result
            if ( results.size () > 0 )
            {
                list.setSelectedIndex ( 0 );
            }

            // Ensure autocomplete popup has correct size
            popup.pack ();

            // Display or hide popup depending on the results
            if ( results.size () > 0 )
            {
                showAutocompletePopup ();
            }
            else
            {
                hideAutocompletePopup ();
            }
        } );
    }

    @Override
    public void focusLost ( final FocusEvent e )
    {
        SwingUtilities.invokeLater ( this::hideAutocompletePopup );
    }

    @Override
    public void keyPressed ( final KeyEvent e )
    {
        if ( e.getKeyCode () == KeyEvent.VK_UP )
        {
            final int index = list.getSelectedIndex ();
            if ( index != -1 && index > 0 )
            {
                list.setSelectedIndex ( index - 1 );
            }
        }
        else if ( e.getKeyCode () == KeyEvent.VK_DOWN )
        {
            final int index = list.getSelectedIndex ();
            if ( index != -1 && list.getModel ().getSize () > index + 1 )
            {
                list.setSelectedIndex ( index + 1 );
            }
        }
        else if ( e.getKeyCode () == KeyEvent.VK_ENTER )
        {
            final String text = ( String ) list.getSelectedValue ();
            setText ( text );
            setCaretPosition ( text.length () );
        }
        else if ( e.getKeyCode () == KeyEvent.VK_ESCAPE )
        {
            hideAutocompletePopup ();
        }
    }

    @Override
    public void insertUpdate ( final DocumentEvent e )
    {
        documentChanged ();
    }

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

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

    @Override
    public void keyTyped ( final KeyEvent e )
    {
        // Do nothing
    }

    @Override
    public void keyReleased ( final KeyEvent e )
    {
        // Do nothing
    }

    /**
     * Custom list model providing data and bridging view update call.
     */
    private class ListModel extends AbstractListModel
    {
        @Override
        public int getSize ()
        {
            return results.size ();
        }

        @Override
        public Object getElementAt ( final int index )
        {
            return results.get ( index );
        }

        /**
         * Properly updates list view.
         */
        public void updateView ()
        {
            super.fireContentsChanged ( AutocompleteField.this, 0, getSize () );
        }
    }

    /**
     * Sample {@link AutocompleteField} usage.
     *
     * @param args run arguments
     */
    public static void main ( final String[] args )
    {
        final JFrame frame = new JFrame ( "Sample autocomplete field" );

        // Sample data list
        final List<String> values = Arrays.asList ( "Frame", "Dialog", "Label", "Tree", "Table", "List", "Field" );

        // Simple lookup based on our data list
        final Function<String, List<String>> lookup = text -> values.stream ()
                .filter ( v -> !text.isEmpty () && v.toLowerCase ().contains ( text.toLowerCase () ) && !v.equals ( text ) )
                .collect ( Collectors.toList () );

        // Autocomplete field itself
        final AutocompleteField field = new AutocompleteField ( lookup );
        field.setColumns ( 15 );

        final JPanel border = new JPanel ( new BorderLayout () );
        border.setBorder ( new EmptyBorder ( 50, 50, 50, 50 ) );
        border.add ( field );
        frame.add ( border );

        frame.setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE );
        frame.pack ();
        frame.setLocationRelativeTo ( null );
        frame.setVisible ( true );
    }
}

因此,在本例中,popupjwindow本身不是活动的(未聚焦),并且无法获得焦点,因为它被强制配置为如此。这允许我们将焦点集中在JTextField中,并继续键入。

在本例中,我们还捕获关键事件,如字段中的向上/向下箭头,以便在自动完成结果中导航。ENTER和ESCAPE用于接受/取消结果选择。

这段代码还可以稍微重写一下,以使用SwingPopupFactory作为自动完成弹出窗口的源代码,但在essense中仍然是一样的,因为PopupFactory使用的HeavyWeightWindow只是扩展了JWindow并添加了一些设置。

 类似资料:
  • 问题内容: 在该线程中,我找到了一种在中实现功能的方法(并且,但与此无关)。 尝试在中实施此功能时,会引发一些异常。以下代码将演示该问题。它是如此简单,与我使用的方式非常相似,效果很好。 有人可以帮我找到解决方案吗? 下面的代码: ( 请注意,我使用) 这是错误消息我得到: ( 这是一个有点长:d ) 问题答案: 您的代码中没有涉及。我认为应该读这样的东西… 如果你想绑定到(以便文本字段将更新的选

  • 我在netbeans中创建了一个桌面应用程序,到昨天为止还很好,但现在需求发生了变化,客户机希望所有的JTextField都是自动完成的,源码是databse,我搜索并试图找到任何适合netbeans的例子,但我无法找到这样的东西,所以请帮助我如何在netbeans中实现自动完成的东西?请帮忙

  • 问题内容: 我想在Java中创建一个自动完成程序,当用户在中键入字符/字符串时,该程序应立即提供建议列表。问题是我对如何做感到困惑。 有人可以提供关于上述问题的想法或样本吗? 问题答案: 1)你必须在使用之前对数组进行排序以获得更好的性能… 2)正如我提到的,你必须接受这两个方面 3)不要忘记为这些组件的更好和最好的工作设置初始值 简单输出 从代码

  • 本文向大家介绍jQuery实现用户输入自动完成功能,包括了jQuery实现用户输入自动完成功能的使用技巧和注意事项,需要的朋友参考一下 利用jQuery UI中Auto-complete插件实现输入自动完成功能,大家在使用诸如淘宝、京东等电商平台搜索商品时,往往只要输入商品的一些特殊字符,就可以显示出和该字符相近的列表菜单,用户使用鼠标或者键盘方向键就可以快速选择,实现了很好的用户体验。 1.最简

  • 本文向大家介绍自动完成的搜索框javascript实现,包括了自动完成的搜索框javascript实现的使用技巧和注意事项,需要的朋友参考一下 在很多需要搜索的网站, 都会有一个自动完成的搜索框. 方便用户查找他们想要的搜索词. 帮助用户快速找到自己想要的结果. 这种方式是比较友好的. 所以是比较提倡使用的. 我们这次就来实现这一效果. 我们通过两篇文章来进行讲解. 首先我们来完成界面的设计布局.

  • 开始 输入城市名称-部分或完整 如果用户点击enter,则从获取文本 开始暴力搜索。 如果找到匹配项,则将它们放入中,并将其放入 如果未找到匹配项,请在中添加 “未找到匹配项” 向包含结果的用户显示 停止 显示结果的的大小(它是中的)根据结果而改变--如果城市名称小,就小,如果城市名称大,就大。 我试过所有可能的组合。我尝试使用、和的,但问题不会解决。 无论如何,我都希望它与修饰的的大小匹配