1

I have Implemented an AutoSuggest for JTextField.It's working fine for JTextField.But When I try to use it for JTable CellEditor.It's throwing

Exception in thread "AWT-EventQueue-0" java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location
    at java.awt.Component.getLocationOnScreen_NoTreeLock(Component.java:2048)
    at java.awt.Component.getLocationOnScreen(Component.java:2022)
    at javax.swing.JPopupMenu.show(JPopupMenu.java:942)

AutoSuggestTable.java

import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import javax.swing.AbstractAction;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

/**
 *
 * @author Phystem
 * @param <T>
 */

public class AutoSuggestTable<T> extends JTextField {

private List<T> items;

private final JComboBox combobox = new JComboBox();

private final DefaultComboBoxModel comboBoxModel;

private final BasicComboPopup suggestPopup;

public AutoSuggestTable() {
    this(new ArrayList<T>());
}

public AutoSuggestTable(List<T> items) {
    this.items = items;
    Collections.sort(items, new Compare());
    comboBoxModel = new DefaultComboBoxModel();
    combobox.setModel(comboBoxModel);
    suggestPopup = new BasicComboPopup(combobox);
    install();
}

private void addToSuggestList(List<Object> list) {
    comboBoxModel.removeAllElements();
    if (list.isEmpty()) {
        suggestPopup.hide();
    } else {
        for (Object elm : list) {
            comboBoxModel.addElement(elm);
        }
        suggestPopup.show(this, 0, getHeight());
        suggestPopup.setPopupSize(getWidth(), suggestPopup.getHeight());
    }
}

public void hidePopup() {
    suggestPopup.hide();
}

/**
 * The value to search for I'm checking for case insensitive contains Modify
 * it if you want
 *
 * @param value
 */
private void filter(String value) {
    List<Object> tempList = new ArrayList<>();
    for (Object item : items) {
        if (item.toString().toLowerCase().contains(value.toLowerCase())) {
            tempList.add(item);
        }
    }
    addToSuggestList(tempList);
}

public List<T> getItems() {
    return items;
}

public void withItems(List<T> items) {
    this.items = items;
    Collections.sort(items, new Compare());
}

public void addItem(T item) {
    items.add(item);
}

public void clearItems() {
    items.clear();
}

private void install() {
    setBorder(null);
    setFocusTraversalKeysEnabled(false);
    getDocument().addDocumentListener(new DocumentListener() {

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

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

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

    });
    getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "commit");
    getActionMap().put("commit", new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            setSelectedItemFromList();
        }
    });
    getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "traversedown");
    getActionMap().put("traversedown", new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            traversedown();
        }
    });
    getInputMap().put(KeyStroke.getKeyStroke("UP"), "traverseup");
    getActionMap().put("traverseup", new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            traverseup();
        }
    });
}

private void setSelectedItemFromList() {
    if (comboBoxModel.getSize() > 0) {
        setText(suggestPopup.getList().getSelectedValue().toString());
        suggestPopup.hide();
    }
}

private void traverseup() {
    if (comboBoxModel.getSize() > 0) {
        int index = suggestPopup.getList().getSelectedIndex() - 1;
        if (index >= 0) {
            suggestPopup.getList().setSelectedIndex(index);
            suggestPopup.getList().ensureIndexIsVisible(index);
        }
    }
}

private void traversedown() {
    if (comboBoxModel.getSize() > 0) {
        int index = suggestPopup.getList().getSelectedIndex() + 1;
        if (index < comboBoxModel.getSize()) {
            suggestPopup.getList().setSelectedIndex(index);
            suggestPopup.getList().ensureIndexIsVisible(index);
        }
    }
}

private void onTextChange(DocumentEvent e) {
    Document source = e.getDocument();
    int length = source.getLength();
    try {
        filter(source.getText(0, length));
    } catch (BadLocationException be) {
        System.out.println("Contents: Unknown");
    }
}

private static class Compare implements Comparator {

    @Override
    public int compare(Object o1, Object o2) {
        return Objects.toString(o1, "").compareTo(Objects.toString(o2, ""));
    }

}
}

MCVE

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultCellEditor;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

/**
 *
 * @author Phystem
 */
public class TestClass {

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {

        @Override
        public void run() {
            JFrame frame = new JFrame("AutoSuggest Test");
            JTable table = getTable();
            AutoSuggestTable autoSuggest = new AutoSuggestTable(getSampleList());
            JPanel panel = new JPanel(new BorderLayout());
            panel.add(new JScrollPane(table), BorderLayout.CENTER);
            panel.add(autoSuggest, BorderLayout.NORTH);
            frame.add(panel);
            table.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(autoSuggest));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    });
}

private static List<String> getSampleList() {
    List<String> list = new ArrayList<>();
    list.add("Apples");
    list.add("Bananas");
    list.add("Grapes");
    list.add("Oranges");
    list.add("PineApples");
    list.add("Watermelon");
    list.add("Lemon");
    return list;
}

private static JTable getTable() {
    DefaultTableModel model = new DefaultTableModel();
    model.addColumn("Test1");
    model.addColumn("Test2");
    for (int i = 0; i < 5; i++) {
        model.addRow(new Object[]{"xx" + i, "yy" + i});
    }
    JTable table = new JTable(model);
    return table;
}
}

For JTable It's throwing the above mentioned error for the first edit and working fine for the second edit. I mean if I double click on the first column it's throwing that error but when I again double click it's showing the autosuggest.

Madhan
  • 5,750
  • 4
  • 28
  • 61
  • This https://community.oracle.com/thread/1362775?start=0&tstart=0 points to this http://www.orbital-computer.de/JComboBox/ . At the bottom of this second link, you can find an example fixing the problem. The reason seems to be that you can not use DefaultCellEditor for your purposes. Hope this helps. – RubioRic Apr 27 '16 at 04:21
  • I wondering if the BasicComboPopup is using the JComboBox as its reference to determine where on the screen it should be displayed. Perhaps instead you should use a JPopupMenu or roll your own popup using a JWindow – MadProgrammer Apr 27 '16 at 04:22
  • @MadProgrammer If I go with `JWindow` then I have to handle the change in location,if the UI is moved. If I go with `JPopupMenu` then the focus will shift to popupmenu and I can't type further in `JTextField`. – Madhan Apr 27 '16 at 04:40
  • @RubioRic that's for JComboBox but I am trying for JTextField – Madhan Apr 27 '16 at 04:42
  • I've use JWindow for this type of thing before, yes, you have to actually calculate where the window is, but it's less likely to blow up in your face like it is now – MadProgrammer Apr 27 '16 at 04:51
  • You're right but the problem with both of them seems to be located in the cell editor. – RubioRic Apr 27 '16 at 04:51

1 Answers1

0

I have used the isShowing method to check if is the editor is visible before showing the popup and it solved the issue.

      if (isShowing()) {
            suggestPopup.show(this, 0, getHeight());
            suggestPopup.setPopupSize(getWidth(), suggestPopup.getHeight());
        }

There are still some modifications that needs to be done.If anyone wants it here is the Gist

Community
  • 1
  • 1
Madhan
  • 5,750
  • 4
  • 28
  • 61