【问题标题】:Auto-Completing JTextField and Arrow Keys自动完成 JTextField 和箭头键
【发布时间】:2012-05-13 12:04:22
【问题描述】:

我正在尝试使用javax.swing.JList 构建一个javax.swing.JTextField,以便像Google 一样自动完成。

  • 当一个写一个字时,Google 显示几个匹配

    •   ▼   我可以使用   ▲    ▼   选择一些 匹配 kbd> 和
    • 可以使用   ◀   ▶   编辑我的输入。
    • 当我按   Enter          键搜索框中的内容。
    • 当按 Esc 时,框会变为原来的输入。

我的申请是关于Bible,我想在学习这个词时寻找一个特定的词。我见过Java2sAutoTextField,但箭头键没有这种特殊行为。

【问题讨论】:

  • 您能否扩展 JTextField 并为其提供一个 JList 作为成员,然后从 JTextField 手动管理按键?另外,下拉列表不会在容器边缘被切断吗? Swing 组件是轻量级的。
  • @mKorbel 如果有更好的解决方案,我想了解更多。实际上,我正在寻找类似于 Google 的行为,不一定是 JList
  • @Paul Vargas 好吧,我会看看,我认为这个问题对于编码来说可能非常简单
  • @mKorbel 目前我有一个JTextField,在JToolBar 中没有自定义。我在 Swing Hacks 一书中看到了一个例子。我正在审查它,因为它在示例中的事件CaretListener 中有一个小问题。
  • @george_h 的回答可能是正确的,最重要的是,

标签: java swing text jtextfield


【解决方案1】:

这需要一个自定义编码组件。绝对是扩展 JTextField 的类,并且在该类中,您有一个包含 JList 的 JPopupMenu。您必须将 JPopupMenu 放在文本字段的正下方,使其看起来像 1 个组件。

您的下一个技巧是在您键入时进行过滤。我通常使用 Java6 TableRowSorter 和一个 JTable 来执行此操作,我在其中预先填充了数据。您将需要 JTextField 上的一些更改侦听器并拦截键入的每个键并获取您的数据。

  1. 按键按下
  2. 在数据库中执行查询(或某些数据存储以获取类似条目)
  3. 用这些整体填充 JTable
  4. 使用基于 JTextField 条目的正则表达式设置 RowFilter 以过滤检索到的数据
  5. 通过关键听众管理您的操作

编辑

我制作了一个示例摇摆应用程序来展示我所说的内容。这是一个复制/粘贴示例,应该可以立即使用(需要 JDK 1.6+)。我基本上得到了你想要的东西,我把 cmets 放在我告诉你填写空白的地方。例如,Escape 键事件被消耗,你可以用它做任何你想做的事情。

initTableModel() 方法只是用数据初始化表模型。通常,您希望使用来自数据库或其他东西的数据动态填充表模型。可以进行很多调整,但这是为了举例说明;)所以这应该是一个足够好的示例,您可以修改以完成您的目标。除此之外,你必须付给我 $$$ :)

package test.text.googleclone;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.regex.PatternSyntaxException;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;

public class SearchAutoFillTest {

private JFrame frame = null;
private JTextField searchField = null;
private JPopupMenu popup = null;

private JTable searchTable = null;
private TableRowSorter<DefaultTableModel> rowSorter = null;
private DefaultTableModel searchTableModel = null;

public SearchAutoFillTest() {
    searchTableModel = new DefaultTableModel();
    initTableModel();

    rowSorter = new TableRowSorter<DefaultTableModel>(searchTableModel);
    searchTable = new JTable(searchTableModel);
    searchTable.setRowSorter(rowSorter);
    searchTable.setFillsViewportHeight(true);
    searchTable.getColumnModel().setColumnSelectionAllowed(false);
    searchTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    searchTable.getTableHeader().setReorderingAllowed(false);
    searchTable.setPreferredSize(new Dimension(775, 100));
    searchTable.setGridColor(Color.WHITE);

    searchField = new JTextField();
    searchField.getDocument().addDocumentListener(new DocumentListener() {
        @Override
        public void changedUpdate(DocumentEvent e) {
            showPopup(e);
        }

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

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

    searchField.addKeyListener(new KeyListener() {
        @Override
        public void keyTyped(KeyEvent e) {

        }

        @Override
        public void keyReleased(KeyEvent e) {
            int code = e.getKeyCode();
            switch(code)
            {
                case KeyEvent.VK_UP:
                {
                    cycleTableSelectionUp();
                    break;
                }

                case KeyEvent.VK_DOWN:
                {
                    cycleTableSelectionDown();
                    break;
                }

                case KeyEvent.VK_LEFT:
                {
                    //Do whatever you want here
                    break;
                }

                case KeyEvent.VK_RIGHT:
                {
                    //Do whatever you want here
                    break;
                }
            }
        }

        @Override
        public void keyPressed(KeyEvent e) {

        }
    });

    KeyStroke keyStroke = KeyStroke.getKeyStroke("ESCAPE");
    searchField.getInputMap().put(keyStroke, "ESCAPE");
    searchField.getActionMap().put("ESCAPE", new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            //Do what you wish here with the escape key.
        }
    });

    popup = new JPopupMenu();
    popup.add(searchTable);
    popup.setVisible(false);
    popup.setBorder(BorderFactory.createEmptyBorder());

    JPanel searchPanel = new JPanel(new BorderLayout(5, 5));
    searchPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
    searchPanel.add(searchField, BorderLayout.CENTER);

    frame = new JFrame();
    frame.setLayout(new BorderLayout(5, 5));
    frame.add(searchPanel, BorderLayout.NORTH);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(800, 500);
    center(frame);
    frame.setVisible(true);
}

private final void newFilter() {
    RowFilter<DefaultTableModel, Object> rf = null;

    try {
        rf = RowFilter.regexFilter(getFilterText(), 0);
    }
    catch(PatternSyntaxException e) {
        return;
    }

    rowSorter.setRowFilter(rf);
}

private final String getFilterText() {
    String orig = searchField.getText();
    return "("+orig.toLowerCase()+")|("+orig.toUpperCase()+")";
}

private void showPopup(DocumentEvent e) {
    if(e.getDocument().getLength() > 0) {
        if(!popup.isVisible()) { 
            Rectangle r = searchField.getBounds();
            popup.show(searchField, (r.x-4), (r.y+16));
            popup.setVisible(true);
        }

        newFilter();
        searchField.grabFocus();

    }
    else {
        popup.setVisible(false);
    }
}

private void cycleTableSelectionUp() {
    ListSelectionModel selModel = searchTable.getSelectionModel();
    int index0 = selModel.getMinSelectionIndex();
    if(index0 > 0) {
        selModel.setSelectionInterval(index0-1, index0-1);
    }
}

private void cycleTableSelectionDown() {
    ListSelectionModel selModel = searchTable.getSelectionModel();
    int index0 = selModel.getMinSelectionIndex();
    if(index0 == -1) {
        selModel.setSelectionInterval(0, 0);
    }
    else if(index0 > -1) {
        selModel.setSelectionInterval(index0+1, index0+1);
    }
}

private void initTableModel() {
    String[] columns = new String[] {"A"};
    String[][] data = new String[][]
    {
        new String[] {"a"},
        new String[] {"aa"},
        new String[] {"aaab"},
        new String[] {"aaabb"},
        new String[] {"aaabbbz"},
        new String[] {"b"},
        new String[] {"bb"},
        new String[] {"bbb"},
        new String[] {"bbbbbbb"},
        new String[] {"bbbbbbbeee"},
        new String[] {"bbbbbbbeeexxx"},
        new String[] {"ccc"},
        new String[] {"cccc"},
        new String[] {"ccccc"},
        new String[] {"cccccaaaa"},
        new String[] {"ccccccaaaa"},
    };

    searchTableModel.setDataVector(data, columns);
}

private void center(Window w) {
    int screenWidth  = Toolkit.getDefaultToolkit().getScreenSize().width;
    int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;

    int windowWidth = w.getWidth();
    int windowHeight = w.getHeight();

    if (windowHeight > screenHeight) {
        return;
    }

    if (windowWidth > screenWidth) {
        return;
    }

    int x = (screenWidth - windowWidth) / 2;
    int y = (screenHeight - windowHeight) / 2;

    w.setLocation(x, y);
}

public static void main(String ... args) {
    new SearchAutoFillTest();
}
}

【讨论】:

  • 我觉得很有趣。我会尝试向您发送反馈。
  • 它确实是一个无私的软件(我不打算向我的朋友收费),所以在某种程度上,你为这项事业做出了贡献。
  • 帅哥,这太棒了..希望我能给更多+1。非常好,谢谢分享..
【解决方案2】:

此组件称为自动完成,包含在所谓的 Swing 扩展项目中。

看看:http://swingx.java.net/
有一个带有演示的 webstart:http://swinglabs-demos.java.net/demos/swingxset6/swingxset.jnlp

【讨论】:

    【解决方案3】:
    • 使用AutoComplete JTextField放入JToolBar/MenuBar,注意使用前必须对ArrayList进行排序,

    • 使用未修饰的 JDialog 代替 JPopup(仍然有一些重要的错误),

      a) 仅创建一个 JDialog,其父级为 JTextField 或 JMenuBar 或 JFrame,

      b) 始终在屏幕上可见 JDialog 之前从 AutoComplete JTextField 搜索 getBounds,此 Bounds 用于在屏幕上正确定位 JDialog

      c) 将 JDialog#setVisible(true) 包装到 invokeLater()

    • 覆盖Escape for JDialog.setVisible(false)

    • 把 JButton 放在那里关闭/隐藏到avoiding overrive rest of important methods on focusLost(这个日历在 focusLost、mouseClick 等方面有很好的解决方法......,用比较器的结果替换日历功能是否很容易,你有下载代码源)

    • 你可以放在那里(我的观点)6 / 9 / 最多 12 个按钮,你可以通过 setBackground(Color.white) 删除 JButton 感觉,例如,你不能,请不要这样做JDialog 和这些 JButton,你的工作就是 setText("result from Comparator")

    • 如果您的 ArrayList for AutoComplete JTextField 已排序,那么您有两个选择

      a) 通过为弹出 JDialog 上的 6 / 9 / 最多 12 个按钮添加 fils 单独数组来为 setText() 添加 fils 单独数组,从 AutoComplete 功能中最容易覆盖偏差,如果你设置背景(Color.white),那么你不关心隐藏没有文字的 JButtons

      b) 另一种方法是创建自己的比较器进行搜索(相同的自动完成功能)前 6 / 9 / 最多 12 个匹配项,

    • 要从 6 / 9 / 最多 12 个 JButton 中捕获事件,请使用 putClientPropertyEventHandlerSwing Actions,您只需要测试文本是否为空 :-),

    • 也许Swing Actions 可能是最好的方法,因为它的事件是可扩展的,并且您可以在默认情况下从此操作中输出enabled/disable(如果JButtons 文本为空)

    【讨论】:

      【解决方案4】:

      听起来您想要JComboBox(请参阅Swing guide)而不是JTextField/JList

      当然,你有一个下拉按钮,但有可能的方法来处理这个 - 见here

      【讨论】:

        【解决方案5】:

        应该是这样的:

        import java.awt.event.KeyEvent;
        import java.awt.event.KeyListener;
        import java.util.ArrayList;
        
        import javax.swing.*;
        
        
        public class Component extends JComponent {
            private final static String[] terms = {"Jesus",
                "Jesus walks on water" //...
            };
            private static ArrayList<String> recent = new ArrayList<String>();
        
            JTextField jtf;
            JList jl;
            public Component(){
                // set up design
                jtf = new JTextField();
                jtf.setSize(this.getWidth() - 25, 25);
                this.add(jtf);
                //...
                // add key listeners
            }
            class Listener implements KeyListener{
        
                @Override
                public void keyPressed(KeyEvent arg0) {
        
                }
        
                @Override
                public void keyReleased(KeyEvent arg0) {
        
                }
        
                @Override
                public void keyTyped(KeyEvent arg0) {
                    if (arg0.getKeyCode() == KeyEvent.VK_DOWN){
                        // set next item on list
                    }
                    else if (arg0.getKeyCode() == KeyEvent.VK_UP){
                        // set previous item on list
                    }
                    else if (arg0.getKeyCode() == KeyEvent.VK_ENTER){
                        // search
                    }
                    else if (arg0.getKeyCode() == KeyEvent.VK_ESCAPE){
                        jtf.setText("");
                    }
                                            else{
                                                 // check list for matches
                                            }
                }
        
            }
        }
        

        【讨论】:

          【解决方案6】:

          默认行为是所有关键事件都转到具有焦点的组件。因此,您需要做的是确定真正应该转到其他组件的密钥并为两者安装KeyListener

          在该侦听器中,您可以将事件转发到其他组件。

          请参阅this answer 如何将事件分派给新组件。在您的情况下,source 必须是另一个组件(列表,如果您的文本字段最初收到事件,反之亦然)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-03-31
            • 2012-01-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-11-25
            • 1970-01-01
            相关资源
            最近更新 更多