Filtering JList based on JTextField

MikkoP picture MikkoP · Feb 7, 2013 · Viewed 9.6k times · Source

I have a JTextField and a JList in my program. The JList contains the user's contacts. I'd like to filter the JList based on the text on the JTextField. For example, if I type in "Mike" it will only show contacts including "Mike". When the user clears the JTextField it would reset the filter.

I know I could do this manually by having two arrays. One for the original contacts and one for the filtered ones. When the user changes the value of the JTextField I would go trought the original list, update the temporary list and update the JList. I just wonder if there is some built in feature to avoid manual labour.

Answer

ATrubka picture ATrubka · Feb 7, 2013

The best way to do things like that is to have a ListModel implementation that filters its contents. I don't know of any default filtering ListModel implementations, but it should not be too hard to do. Here's a quick and dirty solution just to give you an idea. You might want to add more bells and whistles to it.

package test;

import java.util.ArrayList;

import javax.swing.AbstractListModel;
import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

public class FilteredListModel extends AbstractListModel {
    public static interface Filter {
        boolean accept(Object element);
    }

    private final ListModel _source;
    private Filter _filter;
    private final ArrayList<Integer> _indices = new ArrayList<Integer>();

    public FilteredListModel(ListModel source) {
        if (source == null)
            throw new IllegalArgumentException("Source is null");
        _source = source;
        _source.addListDataListener(new ListDataListener() {
            public void intervalRemoved(ListDataEvent e) {
                doFilter();
            }

            public void intervalAdded(ListDataEvent e) {
                doFilter();
            }

            public void contentsChanged(ListDataEvent e) {
                doFilter();
            }
        });
    }

    public void setFilter(Filter f) {
        _filter = f;
        doFilter();
    }

    private void doFilter() {
        _indices.clear();

        Filter f = _filter;
        if (f != null) {
            int count = _source.getSize();
            for (int i = 0; i < count; i++) {
                Object element = _source.getElementAt(i);
                if (f.accept(element)) {
                    _indices.add(i);
                }
            }
            fireContentsChanged(this, 0, getSize() - 1);
        }
    }

    public int getSize() {
        return (_filter != null) ? _indices.size() : _source.getSize();
    }

    public Object getElementAt(int index) {
        return (_filter != null) ? _source.getElementAt(_indices.get(index)) : _source.getElementAt(index);
    }
}

In order to use it you need to set it to your JList and then call setFilter() as you need. Here's an example:

    ListModel source = new DefaultListModel(); // use a model of your choice here;
    FilteredListModel filteredListModel = new FilteredListModel(source);
    JList list = new JList(filteredListModel);
    filteredListModel.setFilter(new FilteredListModel.Filter() {
        public boolean accept(Object element) {
            return false; // put your filtering logic here.
        }
    });

Once method setFilter() is invoked your JList on the screen is expected to change its contents accordingly.

Alternatively, you may want to implement an observer/observable pattern for your Filter, so you can re-filter the list without calling method setFilter(). You can experiment with that later. For the first iteration it's good enough as long as you call method setFilter every time user types something in your JTextField.