我使用了 Trilogy 的答案,并使其能够在 TableCell 内工作。
TableCell 骨架
import javafx.scene.control.Control;
import javafx.scene.control.TableCell;
public abstract class ObjectTableCell<C extends Control,T,S> extends TableCell<T, S>{
private C component;
public ObjectTableCell(C component) {
this.component = component;
}
protected abstract void onEditStart(C component);
protected abstract void onEditCommit();
@Override
public void startEdit() {
super.startEdit();
setGraphic(component);
onEditStart(component);
}
@Override
public void cancelEdit() {
super.cancelEdit();
setGraphic(null);
}
@Override
public void commitEdit(S newValue) {
super.commitEdit(newValue);
setGraphic(null);
onEditCommit();
}
@Override
protected void updateItem(S item, boolean empty) {
super.updateItem(item, empty);
if(empty || item == null) {
setText(null);
}else {
setText(item.toString());
}
}
}
实现示例:
return new ObjectTableCell<AutoCompleteTextField<S>, T, S>(new AutoCompleteTextField<>()) {
@SuppressWarnings("unchecked")
@Override
protected void onEditStart(AutoCompleteTextField<S> component) {
SortedSet<S> entries = new TreeSet<>((S o1, S o2) -> o1.toString().compareTo(o2.toString()));
entries.addAll((Collection<? extends S>) crud.getLastSearchItems());
component.setEntries(entries);
component.getEntryMenu().setOnAction(e -> {
((MenuItem) e.getTarget()).addEventHandler(Event.ANY, event -> {
var lastSelected = component.getSelectedObject() ;
if (lastSelected != null) {
if(!crud.sameId(getItem(), lastSelected)) {
component.setText(lastSelected.toString());
setItem(lastSelected);
commitEdit(lastSelected);
}else {
cancelEdit();
}
}
});
});
component.setSelectedObject(getItem());
var obj = component.getSelectedObject();
component.setText(obj==null? null:obj.toString());
component.requestFocus();
component.selectAll();
}
@Override
protected void onEditCommit() {
TableUI.this.onEditCommit(getSelectedItem());
}
};
这是Trilogy答案的稍作修改版本。
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.TextField;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class AutoCompleteTextField<S> extends TextField {
private final ObjectProperty<S> selectedObject = new SimpleObjectProperty<>();
private SortedSet<S> sortedEntries;
private ObservableList<S> filteredEntries;
private ContextMenu entriesPopup = new ContextMenu();
private String textOccurenceStyle = "-fx-font-weight: bold; " + "-fx-fill: black;";
public AutoCompleteTextField() {
super();
textProperty().addListener((obs, old, niu) -> {
if (getText() == null || getText().length() == 0) {
getFilteredEntries().clear();
getFilteredEntries().addAll(getSortedEntries());
entriesPopup.hide();
} else {
LinkedList<S> searchResult = new LinkedList<>();
Pattern pattern = Pattern.compile(".*" + niu + ".*", Pattern.CASE_INSENSITIVE);
for (S entry : getSortedEntries()) {
Matcher matcher = pattern.matcher(entry.toString());
if (matcher.matches()) {
searchResult.add(entry);
}
}
if (!getSortedEntries().isEmpty()) {
getFilteredEntries().clear();
getFilteredEntries().addAll(searchResult);
populatePopup(searchResult, niu);
if (!entriesPopup.isShowing()) {
entriesPopup.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
}
} else {
entriesPopup.hide();
}
}
});
focusedProperty().addListener((obs, old, niu) -> {
entriesPopup.hide();
});
}
private void populatePopup(List<S> searchResult, String text) {
List<CustomMenuItem> menuItems = new LinkedList<>();
int count = Math.min(searchResult.size(), 8);
for (int i = 0; i < count; i++) {
final String result = searchResult.get(i).toString();
final S itemObject = searchResult.get(i);
int occurence = result.toLowerCase().indexOf(text.toLowerCase());
if (occurence < 0) {
continue;
}
Text pre = new Text(result.substring(0, occurence));
Text in = new Text(result.substring(occurence, occurence + text.length()));
in.setStyle(getTextOccurenceStyle());
Text post = new Text(result.substring(occurence + text.length(), result.length()));
TextFlow entryFlow = new TextFlow(pre, in, post);
CustomMenuItem item = new CustomMenuItem(entryFlow, true);
item.setOnAction((ActionEvent actionEvent) -> {
selectedObject.set(itemObject);
entriesPopup.hide();
});
menuItems.add(item);
}
entriesPopup.getItems().clear();
entriesPopup.getItems().addAll(menuItems);
}
public void setEntries(SortedSet<S> entrySet) {
this.sortedEntries = (entrySet == null ? new TreeSet<>() : entrySet);
setFilteredEntries(FXCollections.observableArrayList(sortedEntries));
}
public ObservableList<S> getFilteredEntries() {
return filteredEntries;
}
public void setFilteredEntries(ObservableList<S> filteredEntries) {
this.filteredEntries = filteredEntries;
}
public SortedSet<S> getSortedEntries() {
return this.sortedEntries;
}
public ObjectProperty<S> selectedObject() {
return selectedObject;
}
public void setSelectedObject(S object) {
selectedObject.set(object);
Platform.runLater(() -> {
entriesPopup.hide();
});
}
public S getSelectedObject() {
return selectedObject.get();
}
public ContextMenu getEntryMenu() {
return entriesPopup;
}
public String getTextOccurenceStyle() {
return textOccurenceStyle;
}
public void setTextOccurenceStyle(String textOccurenceStyle) {
this.textOccurenceStyle = textOccurenceStyle;
}
}
![enter image description here](https://istack.dev59.com/irmFw.webp)
AutoComplete
吗? - Itai