我必须使用具有自动完成功能和多项选择功能的组件,我将附上一张图片以显示我的意思:
我知道基本JavaFx不支持它,但也许您知道我在哪里可以找到如何做的建议。
如果有任何具有此功能的第三方库,请提供链接,或者如果没有,则请提供任何建议 / 想法来帮助我实现它。
自动完成部分已经实现并在此处回答:JavaFX TextField Auto-suggestions,因此请不要提出建议。我对多选部分感兴趣,因此在找到一个要在文本字段中显示的元素之后,我可以寻找其他项目。
答案 0 :(得分:1)
这是结合了自动完成和标记栏属性的解决方案。
public class AutocompleteMultiSelectionBox extends HBox {
private final ObservableList<String> tags;
private final ObservableSet<String> suggestions;
private ContextMenu entriesPopup;
private static final int MAX_ENTRIES = 10;
private final TextField inputTextField;
public AutocompleteMultiSelectionBox() {
getStyleClass().setAll("tag-bar");
getStylesheets().add(getClass().getResource("style.css").toExternalForm());
tags = FXCollections.observableArrayList();
suggestions = FXCollections.observableSet();
inputTextField = new TextField();
this.entriesPopup = new ContextMenu();
setListner();
inputTextField.setOnKeyPressed(event -> {
// Remove last element with backspace
if (event.getCode().equals(KeyCode.BACK_SPACE) && !tags.isEmpty() && inputTextField.getText().isEmpty()) {
String last = tags.get(tags.size() - 1);
suggestions.add(last);
tags.remove(last);
}
});
inputTextField.prefHeightProperty().bind(this.heightProperty());
HBox.setHgrow(inputTextField, Priority.ALWAYS);
inputTextField.setBackground(null);
tags.addListener((ListChangeListener.Change<? extends String> change) -> {
while (change.next()) {
if (change.wasPermutated()) {
ArrayList<Node> newSublist = new ArrayList<>(change.getTo() - change.getFrom());
for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
newSublist.add(null);
}
for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
newSublist.set(change.getPermutation(i), getChildren().get(i));
}
getChildren().subList(change.getFrom(), change.getTo()).clear();
getChildren().addAll(change.getFrom(), newSublist);
} else {
if (change.wasRemoved()) {
getChildren().subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear();
}
if (change.wasAdded()) {
getChildren().addAll(change.getFrom(), change.getAddedSubList().stream().map(Tag::new).collect(
Collectors.toList()));
}
}
}
});
getChildren().add(inputTextField);
}
/**
* Build TextFlow with selected text. Return "case" dependent.
*
* @param text - string with text
* @param filter - string to select in text
* @return - TextFlow
*/
private static TextFlow buildTextFlow(String text, String filter) {
int filterIndex = text.toLowerCase().indexOf(filter.toLowerCase());
Text textBefore = new Text(text.substring(0, filterIndex));
Text textAfter = new Text(text.substring(filterIndex + filter.length()));
Text textFilter = new Text(text.substring(filterIndex,
filterIndex + filter.length())); //instead of "filter" to keep all "case sensitive"
textFilter.setFill(Color.ORANGE);
textFilter.setFont(Font.font("Helvetica", FontWeight.BOLD, 12));
return new TextFlow(textBefore, textFilter, textAfter);
}
/**
* "Suggestion" specific listners
*/
private void setListner() {
//Add "suggestions" by changing text
inputTextField.textProperty().addListener((observable, oldValue, newValue) -> {
//always hide suggestion if nothing has been entered (only "spacebars" are dissalowed in TextFieldWithLengthLimit)
if (newValue.isEmpty()) {
entriesPopup.hide();
} else {
//filter all possible suggestions depends on "Text", case insensitive
List<String> filteredEntries = suggestions.stream()
.filter(e -> e.toLowerCase().contains(newValue.toLowerCase()))
.collect(Collectors.toList());
//some suggestions are found
if (!filteredEntries.isEmpty()) {
//build popup - list of "CustomMenuItem"
populatePopup(filteredEntries, newValue);
if (!entriesPopup.isShowing()) { //optional
entriesPopup.show(this, Side.BOTTOM, 0, 0); //position of popup
}
//no suggestions -> hide
} else {
entriesPopup.hide();
}
}
});
//Hide always by focus-in (optional) and out
focusedProperty().addListener((observableValue, oldValue, newValue) -> entriesPopup.hide());
}
/**
* Populate the entry set with the given search results. Display is limited to 10 entries, for performance.
*
* @param searchResult The set of matching strings.
*/
private void populatePopup(List<String> searchResult, String searchRequest) {
//List of "suggestions"
List<CustomMenuItem> menuItems = new LinkedList<>();
//Build list as set of labels
searchResult.stream()
.limit(MAX_ENTRIES) // Limit to MAX_ENTRIES in the suggestions
.forEach(result -> {
//label with graphic (text flow) to highlight founded subtext in suggestions
TextFlow textFlow = buildTextFlow(result, searchRequest);
textFlow.prefWidthProperty().bind(AutocompleteMultiSelectionBox.this.widthProperty());
CustomMenuItem item = new CustomMenuItem(textFlow, true);
menuItems.add(item);
//if any suggestion is select set it into text and close popup
item.setOnAction(actionEvent -> {
tags.add(result);
suggestions.remove(result);
inputTextField.clear();
entriesPopup.hide();
});
});
//"Refresh" context menu
entriesPopup.getItems().clear();
entriesPopup.getItems().addAll(menuItems);
}
public final ObservableList<String> getTags() {
return tags;
}
public final ObservableSet<String> getSuggestions() {
return suggestions;
}
/**
* Clears then repopulates the entries with the new set of data.
*
* @param suggestions set of items.
*/
public final void setSuggestions(ObservableSet<String> suggestions) {
this.suggestions.clear();
this.suggestions.addAll(suggestions);
}
private class Tag extends HBox {
Tag(String tag) {
// Style
getStyleClass().add("tag");
// Remove item button
Button removeButton = new Button("x");
removeButton.setBackground(null);
removeButton.setOnAction(event -> {
tags.remove(tag);
suggestions.add(tag);
inputTextField.requestFocus();
});
// Displayed text
Text text = new Text(tag);
text.setFill(Color.WHITE);
text.setFont(Font.font(text.getFont().getFamily(), FontWeight.BOLD, text.getFont().getSize()));
// Children position
setAlignment(Pos.CENTER);
setSpacing(5);
setPadding(new Insets(0, 0, 0, 5));
getChildren().addAll(text, removeButton);
}
}
}
.css
.tag-bar {
-fx-border-color: lightblue;
-fx-spacing: 3;
-fx-padding: 3;
-fx-max-height: 30;
}
.tag-bar .tag {
-fx-background-color: -fx-selection-bar;
-fx-border-radius: 5 5 5 5;
}
.tag-bar .tag {
-fx-text-fill: white;
}
.tag-bar .tag .button{
-fx-text-fill: orange;
-fx-font-weight: bold;
}
答案 1 :(得分:0)
添加到@Sunflame的答案中(对Kotlin代码表示抱歉)
如果要添加新项目,请在构造函数上创建弹出窗口后添加此内容
inputTextField.onKeyTyped = EventHandler { event ->
if ("\r" == event.character && inputTextField.text.isNotEmpty()) {
val newTag = inputTextField.text
suggestions.add(newTag)
tags.add(newTag)
inputTextField.text = ""
}
}
感谢您的辛勤工作,这只是添加的次要功能