Flippy Flippy - 20 days ago 8
Java Question

Link filtered Combobox Item to input

In my editable ComboBox which filters my suggestions due to input, I can fire

Button
events. The problem is, if I type one of the names of my items (like
action3
) in the editable field and click on the first suggestion (which also would be
action3
) which is the suitable item, it always fires the ButtonEvent with the first index
action1
, because it has the first index, but the first index is
action1
.

List<EventHandler<ActionEvent>> handlers = Arrays.asList(
this::action1,
this::action2,
this::action3
);


So how do I link like the action1 to my suggestion correct suggestion?

public void onEnter(ActionEvent event){
int index = editableComboBox.getSelectionModel().getSelectedIndex();
if (index >= 0) {
handlers.get(index).handle(event);
}

}


EDIT 1:

My new initialize() Method looks like this:

protected void initialize() {
new AutoCompleteBox<>(autoBox);
ObservableList<ActionEventHandler> data = FXCollections.observableArrayList(
new ActionEventHandler("action1", this::action1),
new ActionEventHandler("action2", this::action2,
new ActionEventHandler("action3", this::action3)
);
autoBox.getItems().setAll(data);
FilteredList<ActionEventHandler> filtered = new FilteredList<>(data);
ComboBox<ActionEventHandler> autoBox = new ComboBox<>(filtered);
autoBox.setOnAction(event -> {
ActionEventHandler h = autoBox.getValue();
if (h != null) {
h.handle(event);
}
});
autoBox.setEditable(true);
autoBox.setConverter(new StringConverter<ActionEventHandler>() {

@Override
public String toString(ActionEventHandler object) {
return object == null ? "" : object.toString();
}

@Override
public ActionEventHandler fromString(String string) {
if (string == null) {
return null;
}
for (ActionEventHandler h : data) {
if (string.equals(h.toString())) {
return h;
}
}
return null;
}

});

autoBox.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
filtered.setPredicate(h -> h.toString().startsWith(newValue));
});

}


And the custom
ComboBox
class:

public class AutoCompleteBox<T> implements EventHandler<KeyEvent> {

private ComboBox comboBox;
private StringBuilder sb;
private ObservableList<T> data;
private boolean moveCaretToPos = false;
private int caretPos;

public AutoCompleteBox(final ComboBox comboBox) {
this.comboBox = comboBox;
sb = new StringBuilder();
data = comboBox.getItems();



this.comboBox.setEditable(true);
this.comboBox.setOnKeyPressed(new EventHandler<KeyEvent>() {

@Override
public void handle(KeyEvent t) {
comboBox.hide();
}
});
this.comboBox.setOnKeyReleased(AutoCompleteBox.this);
}

@Override
public void handle(KeyEvent event) {

if(event.getCode() == KeyCode.UP) {
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.DOWN) {
if(!comboBox.isShowing()) {
comboBox.show();
}
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.BACK_SPACE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
} else if(event.getCode() == KeyCode.DELETE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
}

if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT
|| event.isControlDown() || event.getCode() == KeyCode.HOME
|| event.getCode() == KeyCode.END || event.getCode() == KeyCode.TAB) {
return;
}

ObservableList list = FXCollections.observableArrayList();
for (int i=0; i<data.size(); i++) {
if(data.get(i).toString().toLowerCase().startsWith(
AutoCompleteBox.this.comboBox
.getEditor().getText().toLowerCase())) {
list.add(data.get(i));
}
}
String t = comboBox.getEditor().getText();

comboBox.setItems(list);
comboBox.getEditor().setText(t);
if(!moveCaretToPos) {
caretPos = -1;
}
moveCaret(t.length());
if(!list.isEmpty()) {
comboBox.show();
}
}

private void moveCaret(int textLength) {
if(caretPos == -1) {
comboBox.getEditor().positionCaret(textLength);
} else {
comboBox.getEditor().positionCaret(caretPos);
}
moveCaretToPos = false;
}
}


I can't link the functions (like
action1
to the String
action1
) or what I believe more, I can't combine the custom class with my custom
ComboBox
.

Answer

In this case it seems to be best to use EventHandler<ActionEvent>s as items that return the strings for toString. Add a converter to convert the item to a non-String object.

public class ActionEventHandler implements EventHandler<ActionEvent> {

    private final EventHandler<ActionEvent> eventHandler;
    private final String name;

    public ActionEventHandler(String name, EventHandler<ActionEvent> eventHandler) {
        Objects.requireNonNull(name);
        Objects.requireNonNull(eventHandler);
        this.name = name;
        this.eventHandler = eventHandler;
    }

    @Override
    public String toString() {
        return name;
    }

    @Override
    public void handle(ActionEvent event) {
        eventHandler.handle(event);
    }

}
ObservableList<ActionEventHandler> data = FXCollections.observableArrayList(
        new ActionEventHandler("action1", this::action1),
        new ActionEventHandler("action2", this::action2),
        new ActionEventHandler("action3", this::action3)
);
FilteredList<ActionEventHandler> filtered = new FilteredList<>(data);
ComboBox<ActionEventHandler> comboBox = new ComboBox<>(filtered);
comboBox.setOnAction(event -> {
    ActionEventHandler h = comboBox.getValue();
    if (h != null) {
        h.handle(event);
    }
});
comboBox.setEditable(true);
comboBox.setConverter(new StringConverter<ActionEventHandler>() {

    @Override
    public String toString(ActionEventHandler object) {
        return object == null ? "" : object.toString();
    }

    @Override
    public ActionEventHandler fromString(String string) {
        if (string == null) {
            return null;
        }
        for (ActionEventHandler h : data) {
            if (string.equals(h.toString())) {
                return h;
            }
        }
        return null;
    }

});
comboBox.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
    filtered.setPredicate(h -> h.toString().startsWith(newValue));
});

Edit

The following code should work together with your AutoCompleteBox class.

@FXML
private ComboBox<ActionEventHandler> autoBox;

@FXML
private void initialize() {
    autoBox.getItems().setAll(
            new ActionEventHandler("action1", this::action1),
            new ActionEventHandler("action2", this::action2),
            new ActionEventHandler("action3", this::action3));
    autoBox.setOnAction(event -> {
        ActionEventHandler h = autoBox.getValue();
        if (h != null) {
            h.handle(event);
        }
    });
    autoBox.setConverter(new StringConverter<ActionEventHandler>() {

        @Override
        public String toString(ActionEventHandler object) {
            return object == null ? "" : object.toString();
        }

        @Override
        public ActionEventHandler fromString(String string) {
            if (string == null) {
                return null;
            }
            for (ActionEventHandler h : data) {
                if (string.equals(h.toString())) {
                    return h;
                }
            }
            return null;
        }

    });
    new AutoCompleteBox<>(autoBox);
}

(I haven't checked the details of your AutoCompleteBox class though...)

Comments