Search code examples
javaswingautocompletejtextfieldjdialog

Java Swing: Controlling focus with textfield and autocompletion dialog/menu


I have a JTextField that I'm trying to add an autocompletion menu to. But I'm having issues with how I should handle the focus.

Here's a SSCCE

package test;

import java.awt.Point;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class SSCCE extends JFrame implements DocumentListener {
    private AutocompletionDialog dialog;

    public SSCCE() {
        dialog = new AutocompletionDialog ();

        JTextField textField = new JTextField(20);
        textField.getDocument().addDocumentListener(this);

        add(textField);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new SSCCE().setVisible(true);
            }
        });
    }

    public void insertUpdate(DocumentEvent e) {
        Point p = this.getLocationOnScreen();
        dialog.setLocation(p.x, p.y + 50);
        dialog.setVisible(true);
    }

    public void removeUpdate(DocumentEvent e) { }
    public void changedUpdate(DocumentEvent e) { }

    private class AutocompletionDialog extends JDialog {
        JList<String> list = new JList<>(
                new String[] { "One", "Two", "Three" });

        AutocompletionDialog() {
            setSize(100, 100);
            add(list);
        }
    }
}

Of course there's more logic to it in the real program, but the issue I'm having is that I want to show the autocompletion dialog/menu, but still be able to continue typing in the text field. At the same time, I also want to be able to navigate the menu with the up/down arrows and the enter key, as well as with the mouse, to select one of the completion options.

Can someone please help me with how I should proceed here? Thanks!

EDIT:

Thanks to @camickr's reply I played around a bit with setFocusableWindowState together with an InputMap/ActionMap to always keep the focus in the text field, and manually control the selected list item. The problem is that you get a visual difference when doing it that way compared to if the list had proper focus. See the screen shots.

This is what it looks like if I don't mess with the focus (this is what I want).

dialog with proper focus

This is what it looks like if I run setFocusableWindowState(false)

dialog with no focus

The main differences are the highlight border (darker blue) around the selected list item, but also the blue highlight around the entire dialog. Then there's also the differences in the title bar.

(Don't mind the render artifacts, I'm connecting to a three year old virtual Linux installation using an old NX client)

EDIT 2:

I was afraid that it was the Look and Feel or OS that determined how the selected list item should look (with the highlighted border for example). But it turns out that it is indeed just the cell renderer that makes that call. Knowing that I felt much better about writing my own cell renderer, and I now have a solution that I'm happy with.

This is the code I ended up using:

private class CellRenderer extends DefaultListCellRenderer
{
    @Override
    public Component getListCellRendererComponent(
            JList<?> jlist, Object value, int index,
            boolean isSelected, boolean cellHasFocus) {
        super.getListCellRendererComponent(
                jlist, value, index, isSelected, cellHasFocus);

        if (isSelected) {
            Border border = UIManager.getBorder(
                    "List.focusSelectedCellHighlightBorder");

            if (border == null) {
                border = UIManager.getBorder(
                        "List.focusCellHighlightBorder");
            }

            setBorder(border);
        }

        return this;
    }
}

Solution

  • As I show the auto completion menu the focus is given to that dialog.

    To prevent focus from going to the popup window you can use:

    JDialog dialog = new JDialog(...);
    dialog.setFocusableWindowState( false );
    ...
    dialog.setVisible( true );
    

    I also want to be able to navigate the menu with the up/down arrows and the enter key

    Then you need to override the default Actions provided by the text field. Check out Key Bindings for more information.

    You can also check out: button highlighting using keyboard keys where I just provided a simple example of implementing an Action.