Search code examples
blackberrycollectionsfilteringlistfield

Blackberry: how to filter rows represented by a ListField?


I'm trying to create an app displaying few extra rows on the top of a list of items with a "live search" field on the top:

list

Of course I've tried using a KeywordFilterField with overriden setSize() for that at first. But this didn't work well - because I couldn't control, when and how setSize() was called by the KeywordFilterField and this has given me drawing issues (a row was not drawn when I had a keyword entered and added a new item to the list, etc.)

So I am trying to "go back to the roots" and use a ListField (and a BasicEditField on the top) this time, because there I can control myself, when and how setSize() is called.

Below is my simplified test code - src\mypackage\MyList.java and border.png.

The code works well, except for the filtering issue - which I will describe below.

border.png

package mypackage;

import java.util.*;
import net.rim.device.api.collection.*;
import net.rim.device.api.collection.util.*; 
import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.decor.*;
import net.rim.device.api.util.*;


public class MyList extends UiApplication {
    public static void main(String args[]) {
        MyList app = new MyList();
        app.enterEventDispatcher();
    }

    public MyList() {
        pushScreen(new MyScreen());
    }
} 

class MyScreen extends MainScreen {
    static final int EXTRA_ROWS = 3;

    BasicFilteredList myFilter = new BasicFilteredList();

    Background myBg = BackgroundFactory.createSolidBackground(0x111111);
    StringProvider myProvider = new StringProvider("Search");
    BasicEditField myFind = new BasicEditField(USE_ALL_WIDTH) {
        protected void paint(Graphics g) {
            if (getTextLength() == 0) {
                g.setColor(Color.LIGHTGRAY);
                g.drawText(myProvider.toString(), 0, 0);
            }

            g.setColor(Color.BLACK);
            super.paint(g);
        }
    };

    MyItemList myItems = new MyItemList();

    // add 1 row for the "* Empty *" string
    ListField myList = new ListField(EXTRA_ROWS + 1) {
        protected boolean navigationClick(int status, int time) {
            int index = getSelectedIndex();
            if (index >= EXTRA_ROWS && myItems.size() > 0) {
                String str = ((MyItem) myItems.getAt(
                    index - EXTRA_ROWS)).toString();
                Status.show("You have clicked - " + str);
            }
            return true;
        }
    };

    Border myBorder = BorderFactory.createBitmapBorder(
        new XYEdges(12, 12, 12, 12),
        Bitmap.getBitmapResource("border.png"));

    public MyScreen() {
        getMainManager().setBackground(myBg);

        myFind.setBorder(myBorder);
        setTitle(myFind);

        myItems.doAdd(new MyItem(1, "Eins"));
        myItems.doAdd(new MyItem(2, "Zwei"));
        myItems.doAdd(new MyItem(3, "Drei"));
        myItems.doAdd(new MyItem(4, "Vier"));

        myList.setCallback(new MyListFieldCallback());
        add(myList);

        myFilter.addDataSet(1, myItems.getElements(), "Test",
            BasicFilteredList.COMPARISON_IGNORE_CASE);
    }

    protected boolean keyChar(char key, int status, int time) {
        if (key == Characters.BACKSPACE && myFind.getTextLength() == 0) {
            Status.show("Kill kill kill!");
            return true;
        }

        if (myFind.getTextLength() > 0) {
            System.err.println("XXX filter for " + myFind.getText());
        }

        return super.keyChar(key, status, time);
    }

    private class MyListFieldCallback implements ListFieldCallback {

        public void drawListRow(ListField list, Graphics g, int index, int y, int width) {
            Font i = getFont().derive(Font.ITALIC);

            g.setColor(Color.DIMGRAY);
            g.drawLine(0, y-9, width, y-9);
            g.setColor(Color.WHITE);

            if (index < EXTRA_ROWS) {
                g.setFont(i);
                g.drawText("Add Item", 0, y, DrawStyle.ELLIPSIS|DrawStyle.HCENTER, width);
                return;
            } 

            if (myItems.size() == 0) {
                g.setFont(i);
                g.drawText(list.getEmptyString(), 0, y, DrawStyle.ELLIPSIS|DrawStyle.HCENTER, width);
                return;
            }

            MyItem item = (MyItem) myItems.getAt(index - EXTRA_ROWS);
            g.drawText(item.toString(), 0, y, DrawStyle.ELLIPSIS|DrawStyle.HCENTER, width);
        }

        public Object get(ListField list, int index) { 
            return myItems.getAt(index); 
        }

        public int getPreferredWidth(ListField list) { 
            return Display.getWidth(); 
        }

        public int indexOfList(ListField list, String prefix, int start) { 
            return 0; 
        }
    }

    class MyItemList extends SortedReadableList {
        public MyItemList() {
            super(new MyItem.MyComparator());        
        } 

        protected void doAdd(Object obj) {
            super.doAdd(obj);
            myList.setSize(size() + EXTRA_ROWS);  
        }

        protected boolean doRemove(Object obj) {
            // if the list is empty, add 1 row for "* Empty *" string
            int size = (size() == 0 ? EXTRA_ROWS + 1 : size() - 1 + EXTRA_ROWS);
            myList.setSize(size);
            return super.doRemove(obj);        
        }

        protected Object[] getElements() {
            return super.getElements();
        }
    }
}

class MyItem {
    int _num;
    String _name;

    public MyItem(int num, String name) {
        _num = num;
        _name = name;
    }

    public String toString() {
        return _num + ": " + _name;
    }

    static class MyComparator implements Comparator {
        public int compare(Object obj1, Object obj2) {
            MyItem item1 = (MyItem) obj1;
            MyItem item2 = (MyItem) obj2;

            return item1.toString().compareTo(item2.toString());
        }
    }

    static class MyProvider implements KeywordProvider {
        public String[] getKeywords(Object obj) {
            MyItem item = (MyItem) obj;
            return new String[]{ Integer.toString(item._num), item._name };
        }
    }
}

My problem is that I do not know how to add filtering.

For example when a user enters "E" into myFind, my code will detect this event in the keyChar() and also I understand, that I will have to call myList.setSize(EXTRA_ROWS + 1) because there will be 1 filtered result.

But how do I filter the items displayed by myList, how to implement this?

Maybe I could use BasicFilteredList and its method

public void addDataSet(int id,
                       Object[] objects,
                       String name,
                       long style)

but I'm not sure how to apply it here?

UPDATE:

I've added a BasicFilteredList to my test code above, but am still not sure how to marry it with a ListField.


Solution

  • You have to do the filtering in code somewhere, and then store the result. Override myFind.update(int) to run the filtering when the filtering text changes. In addition to myItems, you need to store myFilteredItems. After filtering, call setSize() on the listfield. The listfield drawing code also needs to draw from the filtered collection. Then you should be set.