9

我正在尝试构建一个javax.swing.JTextFieldwith用于像Googlejavax.swing.JList这样的自动完成功能。

  • 写一个单词时,Google显示几个匹配项

    • 按下时,我可以使用 and和 and  ▼  选择一些匹配项  ▲    ▼  
    •   ◀ 可以使用和编辑我的输入  ▶  
    • 当我   Enter         按键搜索框中的内容时。
    • 当按下Esc该框更改为原始输入。

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

4

6 回答 6

10

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

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

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

编辑

我制作了一个示例摇摆应用程序来展示我所说的。这是一个复制/粘贴示例,应该可以立即使用(需要 JDK 1.6+)。我基本上得到了你想要的东西,我把评论放在我告诉你填写空白的地方。例如,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();
}
}
于 2012-05-11T14:06:47.450 回答
3

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

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

于 2012-05-15T12:27:51.790 回答
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()

  • 为 JDialog.setVisible(false)覆盖Escape

  • 放在那里关闭/隐藏 JButton 以避免覆盖 focusLost 上的其他重要方法(这个日历在 focusLost、mouseClick 等方面有很好的解决方法......,用比较器的结果替换日历功能是否很容易,你必须下载代码源)

  • 你可以放在那里(我的观点)6 / 9 / 最多 12 个按钮,你可以通过 setBackground(Color.white) 删除 JButton Feels 例如,你不能,请不要用 JDialog 和这些 JButtons 做某事,你工作将只是 setText("比较器的结果")

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

    a) 通过在弹出的 JDialog 上为 setText() 添加 fils 单独的数组,为 6 / 9 / max 12 个按钮添加来自 AutoComplete 功能的最简单的覆盖偏差,如果你 setBackground(Color.white),那么你不关心以某种方式隐藏 JButtons文本

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

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

  • 也许Swing Actions可能是最好的方法,因为它的事件是可扩展的,并且您可以默认启用/禁用(如果 JButtons 文本为空)此操作的输出

于 2012-05-15T20:21:26.813 回答
1

听起来您想要一个JComboBox(请参阅Swing 指南)而不是JTextField/ JList

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

于 2012-05-04T15:36:06.153 回答
1

这将是这样的:

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
                                    }
        }

    }
}
于 2012-05-13T20:14:06.280 回答
0

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

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

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

于 2012-05-04T15:24:31.623 回答