Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

combobox jump to typed char

I stumbled on (in my eyes) a silly problem. However I don't find a solution for this (maybe because of not using the right search keywords, or by making it too difficult when it can be easy..) Scenario:

I have a combobox with 500 customers. I have to select a single costumer.

In Swing, when the list was down and you started typing, it automatically jumps to the typed letter. E.g.:

Items:

  • Adam
  • Dirk
  • Freddy
  • ...
  • Roger
  • Steven
  • Z person

When the combobox list is open, I just type 'R' and, in swing, it jumped to the first customer starting with 'R'. In javafx 2 it seems it does not have that behaviour... Is there some option that I have to enable or should I do something like using an editable combobox instead and make a filter() method that is fired on every keypress?

Edit: sollution based on Bhupendra's answer:

public class FilterComboBox<T> extends ComboBox<T> {
private final FilterComboBox<T> fcbo = this;

//private FilterComboBox fcbo = this;
private ObservableList<T> items;
private ObservableList<T> filter;
private String s;
private Object selection;

private class KeyHandler implements EventHandler< KeyEvent> {

    private SingleSelectionModel<T> sm;

    public KeyHandler() {
        sm = getSelectionModel();
        s = "";
    }

    @Override
    public void handle(KeyEvent event) {
        filter.clear();
        // handle non alphanumeric keys like backspace, delete etc
        if (event.getCode() == KeyCode.BACK_SPACE && s.length() > 0) {
            s = s.substring(0, s.length() - 1);
        } else {
            s += event.getText();
        }

        if (s.length() == 0) {
            fcbo.setItems(items);
            sm.selectFirst();
            return;
        }
        //System.out.println(s);
        if (event.getCode().isLetterKey()) {
            for (T item : items) {
                if (item.toString().toUpperCase().startsWith(s.toUpperCase())) {

                    filter.add(item);
                    //System.out.println(item);

                    fcbo.setItems(filter);

                    //sm.clearSelection();
                    //sm.select(item);

                }
            }
            sm.select(0);
        }

    }
}

public FilterComboBox(final ObservableList<T> items) {
    super(items);
    this.items = items;
    this.filter = FXCollections.observableArrayList();

    setOnKeyReleased(new KeyHandler());

    this.focusedProperty().addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
            if (newValue == false) {
                s = "";
                fcbo.setItems(items);
                fcbo.getSelectionModel().select((T)selection);
            }

        }
    });

    this.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
            if (newValue != null) {
                selection = (Object) newValue;
            }

        }
    });
}

}

like image 659
Perneel Avatar asked Nov 13 '12 14:11

Perneel


2 Answers

The simplest form of a filter combo box would be as the code below. But it would need more work to refine it. Also, if the list is huge, as in your case, there might be a performance issues as we are looping thru' the entire collection on each key press.

public class FilterComboBox extends ComboBox< String > {
    private ObservableList< String >    items;

    private class KeyHandler implements EventHandler< KeyEvent > {

        private SingleSelectionModel< String >  sm;
        private String                          s;

        public KeyHandler() {
            sm = getSelectionModel();
            s = "";
        }

        @Override
        public void handle( KeyEvent event ) {
            // handle non alphanumeric keys like backspace, delete etc
            if( event.getCode() == KeyCode.BACK_SPACE && s.length()>0)
                s = s.substring( 0, s.length() - 1 );
            else s += event.getText();

            if( s.length() == 0 ) {
                sm.selectFirst();
                return;
            }
            System.out.println( s );
            for( String item: items ) {
                if( item.startsWith( s ) ) sm.select( item );
            }
        }

    }

    public FilterComboBox( ObservableList< String > items ) {
        super( items );
        this.items = items;

        setOnKeyReleased( new KeyHandler() );
    }
}
like image 181
Bhupen Avatar answered Sep 23 '22 16:09

Bhupen


Wouldn't code like this be sufficient?

    comboBox.setOnKeyReleased(new EventHandler<KeyEvent>() {
        @Override
        public void handle(KeyEvent event) {
            String s = jumpTo(event.getText(), comboBox.getValue(), comboBox.getItems());
            if (s != null) {
                comboBox.setValue(s);
            }
        }
    });

...

static String jumpTo(String keyPressed, String currentlySelected, List<String> items) {
    String key = keyPressed.toUpperCase();
    if (key.matches("^[A-Z]$")) {
        // Only act on letters so that navigating with cursor keys does not
        // try to jump somewhere.
        boolean letterFound = false;
        boolean foundCurrent = currentlySelected == null;
        for (String s : items) {
            if (s.toUpperCase().startsWith(key)) {
                letterFound = true;
                if (foundCurrent) {
                    return s;
                }
                foundCurrent = s.equals(currentlySelected);
            }
        }
        if (letterFound) {
            return jumpTo(keyPressed, null, items);
        }
    }
    return null;
}

This will jump to the first item when you press a letter. If you press that letter again, it jumps to the next item starting with that letter, wrapping back to the first if there are no more items starting with that letter.

like image 40
Robert Avatar answered Sep 24 '22 16:09

Robert