3

I have this simple editable JComboBox which while you type filters the available choices. It almost works except some strange cases, for instance when you type "Sass" it filters the available choice to a single "Sassi di Matera" but if you select it, it selects "Arte ruprestre della Vacamonica" which happens to be item[0] of the original model and not of the filtered one.

I tried debugging it for hours but seems to be some strange updateModel(DefaultComboBoxModel filteredModel) which I am not raising myself.

import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;

public class AutoFilterDocument extends PlainDocument
{
    JComboBox comboBox;
    ComboBoxModel model;
    DefaultComboBoxModel filteredModel;
    JTextComponent editor;
    // flag to indicate if setSelectedItem has been called
    // subsequent calls to remove/insertString should be ignored
    boolean selecting = false;

    public void updateModel(List<String> data)
    {
        model = new DefaultComboBoxModel(data.toArray());
        selecting = true;
        comboBox.setModel(model);
        editor.select(0, 0);
        editor.setCaretPosition(editor.getText().length());
        comboBox.setSelectedItem(null);
        selecting = false;
    }

    public void updateModel(DefaultComboBoxModel filteredModel)
    {
        selecting = true;
        comboBox.setModel(filteredModel);
        editor.select(0, 0);
        editor.setCaretPosition(editor.getText().length());
        selecting = false;
    }

    public AutoFilterDocument(final JComboBox comboBox)
    {
        this.comboBox = comboBox;
        model = comboBox.getModel();
        editor = (JTextComponent) comboBox.getEditor().getEditorComponent();
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException
    {
        if (selecting)
        {
            return;
        }
        String text = getText(0, offs);

        filteredModel = new DefaultComboBoxModel();
        // iterate over all items
        for (int i = 0, n = model.getSize(); i < n; i++)
        {
            Object currentItem = model.getElementAt(i);
            if (currentItem.toString().contains(text))
            {
                filteredModel.addElement(currentItem);
            }
        }
        updateModel(filteredModel);
        super.remove(offs, len);
    }

    @Override
    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException
    {
        // return immediately when selecting an item
        if (selecting || str == null || str.length() == 0)
        {
            return;
        }

        super.insertString(offs, str, a);
        String text = getText(0, offs); 
        lookupItem(text + str);
    }

    private Object lookupItem(String pattern)
    {
        Object selectedItem = comboBox.getModel().getSelectedItem();
        if (selectedItem != null && selectedItem.toString().toLowerCase().trim().contains(pattern.toLowerCase().trim()))
        {
            filteredModel = filter(pattern);
            updateModel(filteredModel);
            comboBox.setSelectedItem(selectedItem);
            return selectedItem;
        }
        else
        {
            filteredModel = new DefaultComboBoxModel();
            for (int i = 0, n = model.getSize(); i < n; i++)
            {
                Object currentItem = model.getElementAt(i);
                if (currentItem.toString().toLowerCase().trim().contains(pattern.toLowerCase().trim()))
                {
                    filteredModel.addElement(currentItem);
                }
            }
        }
        updateModel(filteredModel);
        if (filteredModel.getSize() > 0)
        {
            return filteredModel.getElementAt(0);
        }
        else
        {
            return null;
        }
    }

    public DefaultComboBoxModel filter(String pattern)
    {
        filteredModel = new DefaultComboBoxModel();
        // iterate over all items
        for (int i = 0, n = model.getSize(); i < n; i++)
        {
            Object currentItem = model.getElementAt(i);
            // current item starts with the pattern?
            if (currentItem.toString().toLowerCase().trim().contains(pattern.toLowerCase().trim()))
            {
                filteredModel.addElement(currentItem);
            }
        }
        return filteredModel;
    }

    private static void createAndShowGUI()
    {
        // the combo box (add/modify items if you like to)
        JComboBox comboBox = new JComboBox(new Object[]
        {
            "Arte rupestre della Valcamonica", "Centro storico di Roma", "Santa Maria delle Grazie", "Centro storico di Firenze", "Venezia e la sua laguna", "La piazza del Duomo di Pisa", "Centro storico di San Gimignano", "I Sassi di Matera", "La città di Vicenza e le Ville del Palladio nel Veneto", "Centro storico di Siena", "Centro storico di Napoli", "Crespi d’Adda", "Ferrara città del Rinascimento e il suo delta del Po", "Castel del Monte", "I trulli di Alberobello", "Monumenti paleocristiani di Ravenna", "Centro storico della città di Pienza", "Aree archeologiche di Pompei, Ercolano e Torre Annunziata", "Il Palazzo Reale del XVII sec. di Caserta con il parco, l’Acquedotto vanvitelliano e il Complesso di S. Leucio", "Costiera Amalfitana", "Modena: Cattedrale, Torre Civica e Piazza Grande", "Portovenere, Cinque Terre e Isole di Palmaria, Tino e Tinetto", "Residenze Sabaude", "Su Nuraxi di Barumini", "Area archeologica di Agrigento", "Villa romana del Casale a Piazza Armerina", "Orto Botanico di Padova", "Area archeologica di "
            + "Aquileia e basilica Patriarcale", "Centro storico di Urbino", "Parco Nazionale del Cilento", "Vallo di Diano", "Certosa di Padula", "Villa Adriana a Tivoli (Roma)", "Assisi, la Basilica di San Francesco e altri siti francescani", "Isole Eolie", "Città di Verona", "Villa d'Este a Tivoli (Roma)", "Città Barocche del Val di Noto", "Sacri Monti di Piemonte e Lombardia", "Monte San Giorgio", "Val d'Orcia", "Necropoli etrusche di Cerveteri e Tarquinia", "Siracusa e la Necropoli rocciosa di Pantalica", "Genova: Le Strade Nuove and the system of the Palazzi dei Rolli", "Mantova e Sabbioneta", "Ferrovia retica nel territorio di Albula/Bernina (Italia/Svizzera)", "Dolomiti", "I longobardi in Italia. Luoghi di potere", "Siti palafitticoli preistorici delle alpi", "Centro storico della città di Salisburgo", "Palazzo e giardini di Schönbrunn", "Panorama culturale di Hallstatt-Dachstein, Salzkammergut", "Ferrovia del Semmering", "Centro storico della città di Graz e Castello Eggenberg", "Panorama culturale della Wachau", "Centro storico di Vienna",
            "Panorama cultura del lago di Neusiedl (in comune con l'Ungheria)", "Antichi insediamenti sulle Alpi", "Parco nazionale Kakadu", "Grande barriera corallina", "Regione dei Laghi Willandra", "Regione selvaggia della Tasmania", "Lord Howe Island", "Riserve della foresta pluviale centro orientale", "Parco nazionale Uluru-Kata Tjuta", "Tropici del Queensland", "Baia degli squali", "Isola di Fraser", "Siti australiani dei mammiferi fossili (Riversleigh/Naracoorte)", "Isole Heard e McDonald", "Isola Macquarie", "Area delle Greater Blue Mountains", "Parco nazionale Purnululu", "Royal Exhibition Building e i Carlton Gardens", "Teatro dell'opera di Sydney", "Undici penitenziari costruiti tra il XVIII e il XIX secolo dall'impero britannico", "Costa di Ningaloo", "La Grande muraglia cinese", "Monte Taishan, provincia dello Shandong", "Palazzi Imperiali delle dinastie Ming e Qing a Pechino (Città proibita) e Shenyang (Palazzo Mukden)", "Grotte di Mogao a Dunhuang", "Provincia del Gansu", "Mausoleo del primo Imperatore Qin a Xi'an provincia di Shaanxi",
            "Sito dell'uomo di Pechino a Zhoukoudian Municipalità di Pechino", "Monti Huangshan provincia di Anhui", "Valle del Jiuzhaigou area di interesse scenico e storico provincia dello Sichuan", "Huanglong area di interesse scenico e storico provincia dello Sichuan", "Wulingyuan area di interesse scenico e storico provincia dello Hunan", "Località montana e templi circostanti Chengde provincia di Hebei", "Tempio e cimitero di Confucio e maniero della famiglia Kong a Qufu provincia dello Shandong", "Antico complesso di edifici nelle Monti Wudang provincia di Hubei", "Insieme storico del Palazzo del Potala Lhasa Tibet", "Parco nazionale Lushan provincia dello Jiangxi", "Area scenica del Monte Emei compreso il Buddha gigante di Leshan provincia dello Sichuan", "Città vecchia di Lijiang provincia di Yunnan", "Antica città di Ping Yao provincia di Shanxi", "Giardini classici di Suzhou provincia di Jiangsu", "Palazzo d'Estate e giardino imperiale di Pechino", "Il Tempio del paradiso: un altare sacrificale imperiale a Pechino",
            "Monte Wuyi provincia di Fujian", "Incisioni rupestri di Dazu provincia di Sichuan", "Monte Qincheng e il sistema di irrigazione del Dujiangyan provincia di Sichuan", "Antichi villaggi nell'Anhui meridionale-Xidi e Hongcun", "Grotte di Longmen vicino Luoyang provincia di Henan", "Tombe imperiali delle dinastie Ming e Qing", "Grotte di Yungang a Datong provincia di Shanxi", "Area protetta dei tre fiumi paralleli dello Yunnan", "Città capitali e tombe dell'antico regno Goguryeo", "Il centro storico di Macao", "Santuari del panda gigante nella provincia di Sichuan", "Sito archeologico di Yin Xu nella provincia di Henan", "Diaolou e i villaggi del Kaiping", "Paesaggio carsico della Cina meridionale", "Tulou di Fujian", "Parco nazionale del monte Sanqingshan", "Monte Wutai[1]", "Monumenti storici di Dengfeng", "Paesaggio culturale del lago dell'ovest di Hangzhou", "Sito fossile di Chengjiang", "Sito di Xanadu", "Mont Saint-Michel e la sua baia", "Cattedrale di Chartres", "Palazzo e Parco di Versailles", "Basilica e collina di Vézelay",
            "Grotte di Lascaux nella valle del Vézère", "Palazzo e Parco di Fontainebleau", "Cattedrale Notre-Dame di Amiens", "Teatro romano e dintorni e l'arco di Orange", "Monumenti Romani e Romanici di Arles", "Abbazia di Fontenay", "Saline Reali di Arc-et-Senans e Saline di Salins-les-Bains", "Place Stanislas Place de la Carrière e Place d'Alliance a Nancy", "Abbazia di Saint-Savin sur Gartempe", "Golfo di Porto Capo Girolata Riserva naturale di Scandola e Calanchi di Piana in Corsica", "Acquedotto romano di Pont du Gard", "Grande île nel centro di Strasburgo", "Parigi Argini della Senna", "Cattedrale di Notre-Dame ex Abbazia di Saint-Remi e Palazzo di Tau a Reims", "Cattedrale di Bourges", "Centro storico di Avignone", "Canal du Midi", "Storica città fortificata di Carcassonne", "Pirenei-Mont Perdu", "Strade francesi per Santiago de Compostela", "Sito storico di Lione", "Giurisdizione di Saint-Émilion", "Campanili di Belgio e Francia", "La Valle della Loira tra Sully-sur-Loire e Chalonnes-sur-Loire", "Provins città delle fiere medioevali",
            "La città di Le Havre", "Bordeaux Porto della Luna", "Fortificazioni di Vauban", "Lagune della Nuova Caledonia", "Città episcopale di Albi", "Antichi insediamenti sulle Alpi", "Causses e Cevenne paesaggio culturale agro-pastorale", "Bacino minerario del Nord-Passo di Calais", "Cattedrale di Aquisgrana", "Cattedrale di Spira", "Residenza di Würzburg con i giardini di corte e la piazza della residenza", "Chiesa del pellegrinaggio di Wies", "Castelli di Augustusburg e Falkenlust a Brühl", "Cattedrale di Santa Maria e Chiesa di San Michele a Hildesheim", "Monumenti Romani Cattedrale di San Pietro e Chiesa di Nostra Signora a Treviri", "Città anseatica di Lubecca", "Confini dell'Impero romano: Vallo di Adriano-Limes germanico-retico-Vallo Antonino", "Palazzi e parchi di Potsdam e Berlino", "Abbazia e vecchia cattedrale di Lorsch", "Miniere di Rammelsberg e città storica di Goslar", "Complesso monastico di Maulbronn", "Città di Bamberga", "Chiesa collegiata castello e città vecchia di Quedlinburg", "Fonderie di Völklingen",
            "Sito fossile del Pozzo di Messel", "Duomo di Colonia", "Il Bauhaus e i suoi siti a Weimar e Dessau", "Città luterane di Eisleben e Wittenberg", "Weimar classica", "Isola dei musei (Museumsinsel) Berlino", "Castello di Wartburg", "Regno giardino di Dessau-Wörlitz", "Isola di Reichenau", "Complesso industriale delle Miniere di carbone dello Zollverein ad Essen", "Gola del Reno fra Coblenza e Bingen", "Centri storici di Stralsund e Wismar", "Il municipio e la statua sulla piazza del mercato di Brema", "Il parco di Bad Muskau / Park Muzakowski", "La valle dell'Elba a Dresda", "Città vecchia di Ratisbona", "Foreste primordiali dei faggi dei Carpazi e Germania", "Residenze in stile moderno di Berlino", "Wattenmeer", "Officine Fagus ad Alfeld an der Leine", "Antichi insediamenti sulle Alpi", "Teatro dell'Opera margraviale di Bayreuth", "Monumenti buddhisti nella regione di Horyu-ji", "Castello di Himeji", "Shirakami-Sanchi", "Yakushima", "Monumenti storici dell'antica Kyoto (città di Kyoto Uji ed Ōtsu)", "Villaggi storici di Shirakawa-go e Gokayama",
            "Memoriale della pace di Hiroshima", "Santuario shintoista di Itsukushima"
        });
        comboBox.setEditable(true);
        JTextComponent editor = (JTextComponent) comboBox.getEditor().getEditorComponent();
        editor.setDocument(new AutoFilterDocument(comboBox));

        // create and show a window containing the combo box
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(3);
        frame.getContentPane().add(comboBox);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args)
    {
        javax.swing.SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

--NOTICE--

Please do not suggest other GUI components if:

  • they are not Swing
  • they don't have the same functionality as the one above: filter comboBox by partial string, dropdown filtered by partial name, on click selects element etc etc.
  • they are partially working or do slightly different things
dendini
  • 3,842
  • 9
  • 37
  • 74
  • 3
    if combo box model is empty DefaultComboBoxModel#addElement calls DefaultComboBoxModel#setSeletedItem with the first item entered. When you type sass and select the result first it clear the text field so you remove method will be called with a empty string. so every item in the original model is added to the new filtered model and first item (Arte...) is selected. Also look AutoCompleteDecorator in swingX which offers the functionality you needed. – Thinesh Ganeshalingam Jul 16 '13 at 16:43
  • Can you elaborate on that? I still cannot recognize when the remove(int offs, int len) method is called, also there's no empty string but remove is always called with an offset... – dendini Jul 30 '13 at 12:42
  • Can you please fix your code block so that I can save/compile/run it to debug. Currently there are uneven brackets, missing imports/class definition. – Robadob Jul 30 '13 at 18:17
  • The code block was missing imports which can be added automatically with NetBeans, anyway I added them so now it compiles as is. No uneven brackets, I just compiled it and it works well.. – dendini Jul 31 '13 at 06:35
  • See [this](http://stackoverflow.com/questions/15219625/how-would-be-implements-autosugesion-in-jtextarea-swing/15220056#15220056) similar answer – David Kroukamp Jul 31 '13 at 19:35

2 Answers2

2

Ok never mind.. I just found out exactly what I needed:

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import org.apache.log4j.Logger;

/**
 * Autocomplete combobox with filtering and text inserting of new text
 *
 * @author Exterminator13
 */
public class AutoCompleteCombo extends JComboBox
{
    private static final Logger logger = Logger.getLogger(AutoCompleteCombo.class);
    private Model model = new Model();
    private final JTextComponent textComponent = (JTextComponent) getEditor().getEditorComponent();
    private boolean modelFilling = false;
    private boolean updatePopup;

    public AutoCompleteCombo()
    {
        setEditable(true);
        setPattern(null);
        updatePopup = false;

        textComponent.setDocument(new AutoCompleteDocument());
        setModel(model);
        setSelectedItem(null);

        new Timer(20, new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (updatePopup && isDisplayable())
                {
                    setPopupVisible(false);
                    if (model.getSize() > 0)
                    {
                        setPopupVisible(true);
                    }
                    updatePopup = false;
                }
            }
        }).start();
    }

    private class AutoCompleteDocument extends PlainDocument
    {
        boolean arrowKeyPressed = false;
        public AutoCompleteDocument()
        {
            textComponent.addKeyListener(new KeyAdapter()
            {
                @Override
                public void keyPressed(KeyEvent e)
                {
                    int key = e.getKeyCode();
                    if (key == KeyEvent.VK_ENTER)
                    {
                        logger.debug("[key listener] enter key pressed");
                        //there is no such element in the model for now
                        String text = textComponent.getText();
                        if (!model.data.contains(text))
                        {
                            logger.debug("addToTop() called from keyPressed()");
                            addToTop(text);
                        }
                    }
                    else if (key == KeyEvent.VK_UP
                            || key == KeyEvent.VK_DOWN)
                    {
                        arrowKeyPressed = true;
                        logger.debug("arrow key pressed");
                    }
                }
            });
        }

        void updateModel() throws BadLocationException
        {
            String textToMatch = getText(0, getLength());
            logger.debug("setPattern() called from updateModel()");
            setPattern(textToMatch);
        }

        @Override
        public void remove(int offs, int len) throws BadLocationException
        {
            if (modelFilling)
            {
                logger.debug("[remove] model is being filled now");
                return;
            }
            super.remove(offs, len);
            if (arrowKeyPressed)
            {
                arrowKeyPressed = false;
                logger.debug("[remove] arrow key was pressed, updateModel() was NOT called");
            }
            else
            {
                logger.debug("[remove] calling updateModel()");
                updateModel();
            }
            clearSelection();
        }

        @Override
        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException
        {
            if (modelFilling)
            {
                logger.debug("[insert] model is being filled now");
                return;
            }
            // insert the string into the document
            super.insertString(offs, str, a);

            String text = getText(0, getLength());
            if (arrowKeyPressed)
            {
                logger.debug("[insert] arrow key was pressed, updateModel() was NOT called");
                model.setSelectedItem(text);
                logger.debug(String.format("[insert] model.setSelectedItem(%s)", text));
                arrowKeyPressed = false;
            }
            else if (!text.equals(getSelectedItem()))
            {
                logger.debug("[insert] calling updateModel()");
                updateModel();
            }

            clearSelection();
        }
    }

    public void setText(String text)
    {
        if (model.data.contains(text))
        {
            setSelectedItem(text);
        }
        else
        {
            addToTop(text);
            setSelectedIndex(0);
        }
    }

    public String getText()
    {
        return getEditor().getItem().toString();
    }
    private String previousPattern = null;

    private void setPattern(String pattern)
    {

        if (pattern != null && pattern.trim().isEmpty())
        {
            pattern = null;
        }

        if (previousPattern == null && pattern == null
                || pattern != null && pattern.equals(previousPattern))
        {
            logger.debug("[setPatter] pattern is the same as previous: " + previousPattern);
            return;
        }
        previousPattern = pattern;
        modelFilling = true;
        model.setPattern(pattern);

        if (logger.isDebugEnabled())
        {
            StringBuilder b = new StringBuilder(100);
            b.append("pattern filter '").append(pattern == null ? "null" : pattern).append("' set:\n");
            for (int i = 0; i < model.getSize(); i++)
            {
                b.append(", ").append('[').append(model.getElementAt(i)).append(']');
            }
            int ind = b.indexOf(", ");
            if (ind != -1)
            {
                b.delete(ind, ind + 2);
            }
            logger.debug(b);
        }
        modelFilling = false;
        if (pattern != null)
        {
            updatePopup = true;
        }
    }

    private void clearSelection()
    {
        int i = getText().length();
        textComponent.setSelectionStart(i);
        textComponent.setSelectionEnd(i);
    }

    public synchronized void addToTop(String aString)
    {
        model.addToTop(aString);
    }

    private class Model extends AbstractListModel implements ComboBoxModel
    {
        String selected;
        final String delimiter = ";;;";
        final int limit = 20;

        class Data
        {
            private List<String> list = new ArrayList<String>(limit);
            private List<String> lowercase = new ArrayList<String>(limit);
            private List<String> filtered;

            void add(String s)
            {
                list.add(s);
                lowercase.add(s.toLowerCase());
            }

            void addToTop(String s)
            {
                list.add(0, s);
                lowercase.add(0, s.toLowerCase());
            }

            void remove(int index)
            {
                list.remove(index);
                lowercase.remove(index);
            }

            List<String> getList()
            {
                return list;
            }

            List<String> getFiltered()
            {
                if (filtered == null)
                {
                    filtered = list;
                }
                return filtered;
            }

            int size()
            {
                return list.size();
            }

            void setPattern(String pattern)
            {
                if (pattern == null || pattern.isEmpty())
                {
                    filtered = list;
                    AutoCompleteCombo.this.setSelectedItem(model.getElementAt(0));
                    logger.debug(String.format("[setPattern] combo.setSelectedItem(null)"));
                }
                else
                {
                    filtered = new ArrayList<String>(limit);
                    pattern = pattern.toLowerCase();
                    for (int i = 0; i < lowercase.size(); i++)
                    {
                        //case insensitive search
                        if (lowercase.get(i).contains(pattern))
                        {
                            filtered.add(list.get(i));
                        }
                    }
                    AutoCompleteCombo.this.setSelectedItem(pattern);
                    logger.debug(String.format("[setPattern] combo.setSelectedItem(%s)", pattern));
                }
                logger.debug(String.format("pattern:'%s', filtered: %s", pattern, filtered));
            }

            boolean contains(String s)
            {
                if (s == null || s.trim().isEmpty())
                {
                    return true;
                }
                s = s.toLowerCase();
                for (String item : lowercase)
                {
                    if (item.equals(s))
                    {
                        return true;
                    }
                }
                return false;
            }
        }
        Data data = new Data();

        void readData()
        {
            String[] countries =
            {
                "Afghanistan",
                "Albania",
                "Algeria",
                "Andorra",
                "Angola",
                "Argentina",
                "Armenia",
                "Austria",
                "Azerbaijan",
                "Bahamas",
                "Bahrain",
                "Bangladesh",
                "Barbados",
                "Belarus",
                "Belgium",
                "Benin",
                "Bhutan",
                "Bolivia",
                "Bosnia & Herzegovina",
                "Botswana",
                "Brazil",
                "Bulgaria",
                "Burkina Faso",
                "Burma",
                "Burundi",
                "Cambodia",
                "Cameroon",
                "Canada",
                "China",
                "Colombia",
                "Comoros",
                "Congo",
                "Croatia",
                "Cuba",
                "Cyprus",
                "Czech Republic",
                "Denmark",
                "Georgia",
                "Germany",
                "Ghana",
                "Great Britain",
                "Greece",
                "Somalia",
                "Spain",
                "Sri Lanka",
                "Sudan",
                "Suriname",
                "Swaziland",
                "Sweden",
                "Switzerland",
                "Syria",
                "Uganda",
                "Ukraine",
                "United Arab Emirates",
                "United Kingdom",
                "United States",
                "Uruguay",
                "Uzbekistan",
                "Vanuatu",
                "Venezuela",
                "Vietnam",
                "Yemen",
                "Zaire",
                "Zambia",
                "Zimbabwe"
            };

            for (String country : countries)
            {
                data.add(country);
            }
        }
        boolean isThreadStarted = false;

        void writeData()
        {
            StringBuilder b = new StringBuilder(limit * 60);

            for (String url : data.getList())
            {
                b.append(delimiter).append(url);
            }
            b.delete(0, delimiter.length());

            //waiting thread is already being run
            if (isThreadStarted)
            {
                return;
            }
            //we do saving in different thread
            //for optimization reasons (saving may take much time)
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    //we do sleep because saving operation
                    //may occur more than one per waiting period
                    try
                    {
                        Thread.sleep(2000);
                    }
                    catch (InterruptedException ex)
                    {
                    }
                    //we need this synchronization to
                    //synchronize with AutoCompleteCombo.addElement method
                    //(race condition may occur)
                    synchronized (AutoCompleteCombo.this)
                    {

                        //HERE MUST BE SAVING OPERATION
                        //(SAVING INTO FILE OR SOMETHING)
                        //don't forget replace readData() method
                        //to read saved data when creating bean

                        isThreadStarted = false;
                    }
                }
            }).start();
            isThreadStarted = true;
        }

        public Model()
        {
            readData();
        }

        public void setPattern(String pattern)
        {
            int size1 = getSize();
            data.setPattern(pattern);
            int size2 = getSize();
            if (size1 < size2)
            {
                fireIntervalAdded(this, size1, size2 - 1);
                fireContentsChanged(this, 0, size1 - 1);
            }
            else if (size1 > size2)
            {
                fireIntervalRemoved(this, size2, size1 - 1);
                fireContentsChanged(this, 0, size2 - 1);
            }
        }

        public void addToTop(String aString)
        {
            if (aString == null || data.contains(aString))
            {
                return;
            }
            if (data.size() == 0)
            {
                data.add(aString);
            }
            else
            {
                data.addToTop(aString);
            }

            while (data.size() > limit)
            {
                int index = data.size() - 1;
                data.remove(index);
            }

            setPattern(null);
            model.setSelectedItem(aString);
            logger.debug(String.format("[addToTop] model.setSelectedItem(%s)", aString));

            //saving into options
            if (data.size() > 0)
            {
                writeData();
            }
        }

        @Override
        public Object getSelectedItem()
        {
            return selected;
        }

        @Override
        public void setSelectedItem(Object anObject)
        {
            if ((selected != null && !selected.equals(anObject))
                    || selected == null && anObject != null)
            {
                selected = (String) anObject;
                fireContentsChanged(this, -1, -1);
            }
        }

        @Override
        public int getSize()
        {
            return data.getFiltered().size();
        }

        @Override
        public Object getElementAt(int index)
        {
            return data.getFiltered().get(index);
        }
    }

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

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new GridLayout(3, 1));
                final JLabel label = new JLabel("label ");
                frame.add(label);
                final AutoCompleteCombo combo = new AutoCompleteCombo();
                frame.add(combo);
                frame.pack();
                frame.setSize(500, frame.getHeight());
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
dendini
  • 3,842
  • 9
  • 37
  • 74
  • This is a bad solution - `KeyListener` is not avsiable, key bindings would be better. The document model shouldn't be making modifications to any other part of the MCV, besides, a `DocumentFilter` would be a better solution to creating your own `Document`. IMHO, you'd actually be better of with a proxy model that filtered the original model, it would be more re-usable, but that's just me – MadProgrammer Aug 03 '13 at 01:07
  • Incredibly this is the only working example I have found, I tried combining one myself but JComboBox events are just a crazy hell. If you have some better running one please share it. – dendini Aug 03 '13 at 07:57
  • 1
    I didn't say it was easy, just that I violates a bunch of principles and responsibilities. I got fed up and wrote my own component ;) – MadProgrammer Aug 03 '13 at 08:50
-1

yes I don't think if you have set textComponent the Document obj, the KeyListener is redundant. Set DocumentListener will do the trick

Saintor
  • 1
  • 3