Sunday, September 13, 2009

Lost Again: Searchable List With Text Field

I'm really excited about the final season of Lost as is probably evidence by this and my previous demo on the subject, the new demo celebrating the upcoming season is now within the incubator SVN and in the video on youtube to your right.
This demo shows off an ability often requested by people, searching within a list without actually using a text field that "steals" the focus. As you can see a standard LWUIT text field is drawn on top of the List and accepts user input as it always does thus allowing me to narrow my search within a large unordered list.
This is accomplished rather easily by painting the text field on top of the list and redirecting game key events to the list while all others are sent to the text field.

The text field is hidden after 1000 milliseconds of no activity, no need for glasspane or anything of that magnitude. You do need the latest LWUIT trunk for this to work since text field assumed it has a parent from when getting input (which isn't the case here). To workaround this you can override install/remove commands in text field with an empty implementation.

List l = new List(LIST_DATA) {
private long lastSearchInteraction;
private TextField search = new TextField(3);
{
search.getSelectedStyle().setBgTransparency(255);
search.setReplaceMenu(false);
search.setInputModeOrder(new String[]{"Abc"});
search.setFocus(true);
}

public void keyPressed(int code) {
int game = Display.getInstance().getGameAction(code);
if (game > 0) {
super.keyPressed(code);
} else {
search.keyPressed(code);
lastSearchInteraction = System.currentTimeMillis();
}
}

public void keyReleased(int code) {
int game = Display.getInstance().getGameAction(code);
if (game > 0) {
super.keyReleased(code);
} else {
search.keyReleased(code);
lastSearchInteraction = System.currentTimeMillis();
String t = search.getText();
int modelSize = getModel().getSize();
for(int iter = 0 ; iter < modelSize ; iter++) {
String v = getModel().getItemAt(iter).toString();
if(v.startsWith(t)) {
setSelectedIndex(iter);
return;
}
}
}
}

public void paint(Graphics g) {
super.paint(g);
if (System.currentTimeMillis() - 1000 < lastSearchInteraction || search.isPendingCommit()) {
search.setSize(search.getPreferredSize());
Style s = search.getStyle();
search.setX(getX() + getWidth() - search.getWidth() - s.getPadding(RIGHT) - s.getMargin(RIGHT));
search.setY(getScrollY() + getY());
search.paintComponent(g, true);
}
}

public boolean animate() {
boolean val = super.animate();
if(lastSearchInteraction != -1) {
search.animate();
if(System.currentTimeMillis() - 1000 > lastSearchInteraction && !search.isPendingCommit()) {
lastSearchInteraction = -1;
search.clear();
}
return true;
}
return val;
}
};

18 comments:

  1. i used existing searching technique.
    i need to change this new searching function.
    how i can implement in my application?

    Thanks in advance.

    Bharath

    ReplyDelete
  2. Hi Shai,

    First off, love the framework. I've been designing a developing speech recognition app for Vlingo using it, and was able to prototype a full UI in less than a week!

    Is there a discussion board somewhere where I can ask questions and provide feedback? I have a few questions and a few helpful comments on your API design that I'd like to share.

    Cheers,
    Joe

    ReplyDelete
  3. Hello Shai, in my application I had to use registerAnimated() for my List component, in your Demo app, it worked without registerAnimated(). Could you clarify why your example works without registerAnimated()? The behaviour in my application while I didn't registerAnimated() caused me some headache :-)

    Regards from Germany
    Andreas

    ReplyDelete
  4. @Andreas when smooth animation is enabled (it is by default) a list automatically registers itself as animated. So this works for me without registering animated since I'm deriving from List (which does that in its initComponent method).

    ReplyDelete
  5. Oh, that's the ticket...I explicitly set smooth animation to off for testing performance and didn't care to set it on again, thanks for the heads-up. May I take this opportunity to thank you for all your efforts with LWUIT and your always helpful posts and answers.

    Regards from Germany
    Andreas

    ReplyDelete
  6. Hi Shai,

    I am trying to use your example, but when I press any character the textbox appears with that symbol and right after the following exception is thrown:
    java.lang.NullPointerException
    at com.sun.lwuit.TextField.installCommands(TextField.java:954)
    at com.sun.lwuit.TextField.keyPressed(TextField.java:916)
    at com.periodic.ui.ListForm$1.keyPressed(ListForm.java:49)
    at com.sun.lwuit.Form.keyPressed(Form.java:1524)

    If I ignore this exception everything works fine, the textbox just doesn't appear anymore, but the search feature works.

    ReplyDelete
  7. @Untit1ed: Please read the last two sentences of the post...

    ReplyDelete
  8. Thanks, it works... kinda. Text box is not floating though. It always appears at the beginning of the list, so if I scroll one screen down, it won't be visible.

    I looked at getScrollY() and getY() of my List, but they both always return zeroes.

    ReplyDelete
  9. This line helped though, thanks.

    search.setY(-1*getAbsoluteY()+46);
    getAbsoluteY() returned "46" for the first item in my list, and was decreasing with further list items.

    ReplyDelete
  10. @Untit1ed: you need to set your form to scrollable false. The form is scrolling instead of the list...

    ReplyDelete
  11. Hi,
    that looks fantastic. But is there a possibilty to use instead of Strings complete TextFields as items in a List? As far as i know isn't that possible. Am I right? I do not need the search on-the-fly, but a List with TextFields.

    Thanks.

    Best regards from Finland,
    Matthias

    ReplyDelete
  12. @Matthias: you don't need a list for that, just use a container with Box Y layout. Using a list will provide no benefit.

    ReplyDelete
  13. hi, i'm colombian.
    i need your help. please show me , how do you do searchable list whit TextField?
    i don¨t understand :s
    i need the full code =D please!!!!!

    ReplyDelete
  14. Hi,
    I know this post is old. But I saw the code and what it does and I think It's wonderful.

    I implemented my own searchable list but I have a problem.

    When I press a number in the keyboard it appear the letter that correspond with that key. But, for example, if I want to enter the word mom, I have to press the number 6 first one time, then two times and finally three times, to get mom. But at the end what I get is just m. Do you understand my problem?. I'm sorry, I don't know how English very well.

    ReplyDelete
  15. You need to increase the delay from 1000 which makes typing longer words harder to 2000 or even 3000.

    ReplyDelete
  16. This is terrific, but I have one issue which I want to share a fix for. I used this code to modify the ComboBox's popup list box. Multi-click on a single key isn't working when the ComboBox is inside a Dialog because it seems that the animate() routine is not called.

    I got this to work by adding these routines stolen from TextField into the code:
    protected void initComponent() {
    getComponentForm().registerAnimated(this);
    }

    protected void deinitialize() {
    getComponentForm().deregisterAnimated(this);
    }

    ReplyDelete
  17. Yes its possible to use checkboxes in such a list using a renderer and model data.

    Since I wrote this we optimized LWUIT to not register animations implicitly. This broke some compatibility since it required calls to register animated.

    ReplyDelete
  18. hi dear shai
    i want to show T9 mode in menubar after keypress .i set "setReplaceMenu=true" but nothing happen.
    whats the problem?

    ReplyDelete