我想实现一个自动完成功能。目前我有一个包含JTextField的JPanel,当用户开始键入时,会出现一个包含几个选项的autocomplete(JPopupMenu)。
问题是它占用了文本字段的焦点,用户无法再键入。当我将焦点返回到文本字段时,用户不再有选项之间的导航(使用向上和向下按钮)。此外,对菜单的焦点不允许我截取它的KeyListener(不知道为什么),并且当我尝试处理文本字段端的输入时,我在尝试选择菜单项时遇到了问题。
所以我想要:
是否可以处理菜单上的键盘事件并将键入事件转发回文本字段?
处理我的问题的正确方法是什么?
下面是代码。提前道谢!
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);
}
}
我个人建议使用弹出窗口或自定义的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,则从获取文本 开始暴力搜索。 如果找到匹配项,则将它们放入中,并将其放入 如果未找到匹配项,请在中添加 “未找到匹配项” 向包含结果的用户显示 停止 显示结果的的大小(它是中的)根据结果而改变--如果城市名称小,就小,如果城市名称大,就大。 我试过所有可能的组合。我尝试使用、和的,但问题不会解决。 无论如何,我都希望它与修饰的的大小匹配