我想让这个 TextField 具有建议功能,就像 Lucene 中一样。我搜索了整个网络,只找到了 ComboBox。
TextField instNameTxtFld = instNameTxtFld();
private TextField instNameTxtFld() {
TextField txtFld = new TextField();
txtFld.setPrefSize(600, 75);
return txtFld;
}
我无法使用ComboBox的方法是因为如果我使用ComboBox,我无法将值输入到下面的数据库中。
private void goNext() {
if (nameTxtFld.getText() == null || nameTxtFld.getText().trim().isEmpty()
|| instNameTxtFld.getText()== null || instNameTxtFld.getText().trim().isEmpty()
|| addTxtArea.getText() == null || addTxtArea.getText().trim().isEmpty()) {
alertDialog.showAndWait();
} else {
String satu = idNumTxtFld.getText();
String dua = nameTxtFld.getText();
String tiga = addTxtArea.getText();
String empat = instNameTxtFld.getText();
int delapan = idType.getSelectionModel().getSelectedIndex();
String sembilan = timeStamp.getText();
try {
KonekDB.createConnection();
Statement st = KonekDB.conn.createStatement();
String sql = "INSERT INTO privateguest"
+ "(idNumber, name, address, institution, idType, startTime) "
+ "VALUES "
+ "('" + satu + "','" + dua + "','" + tiga + "','" + empat + "','" + delapan + "','" + sembilan + "')";
System.out.println(sql);
st.executeUpdate(sql);
} catch (SQLException ex) {
System.out.println(satu + " " + dua + " " + tiga + " " + empat + " " + delapan + " " + sembilan);
System.out.println("SQL Exception (next)");
ex.printStackTrace();
}
Frame3Private frame3 = new Frame3Private(english);
this.getScene().setRoot(frame3);
}
}
请帮助我制作最简单的代码来执行文本字段建议/自动完成。
这是我基于This的解决方案。
public class AutocompletionlTextField extends TextFieldWithLengthLimit {
//Local variables
//entries to autocomplete
private final SortedSet<String> entries;
//popup GUI
private ContextMenu entriesPopup;
public AutocompletionlTextField() {
super();
this.entries = new TreeSet<>();
this.entriesPopup = new ContextMenu();
setListner();
}
/**
* wrapper for default constructor with setting of "TextFieldWithLengthLimit" LengthLimit
*
* @param lengthLimit
*/
public AutocompletionlTextField(int lengthLimit) {
this();
super.setLengthLimit(lengthLimit);
}
/**
* "Suggestion" specific listners
*/
private void setListner() {
//Add "suggestions" by changing text
textProperty().addListener((observable, oldValue, newValue) -> {
String enteredText = getText();
//always hide suggestion if nothing has been entered (only "spacebars" are dissalowed in TextFieldWithLengthLimit)
if (enteredText == null || enteredText.isEmpty()) {
entriesPopup.hide();
} else {
//filter all possible suggestions depends on "Text", case insensitive
List<String> filteredEntries = entries.stream()
.filter(e -> e.toLowerCase().contains(enteredText.toLowerCase()))
.collect(Collectors.toList());
//some suggestions are found
if (!filteredEntries.isEmpty()) {
//build popup - list of "CustomMenuItem"
populatePopup(filteredEntries, enteredText);
if (!entriesPopup.isShowing()) { //optional
entriesPopup.show(AutocompletionlTextField.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 searchReauest) {
//List of "suggestions"
List<CustomMenuItem> menuItems = new LinkedList<>();
//List size - 10 or founded suggestions count
int maxEntries = 10;
int count = Math.min(searchResult.size(), maxEntries);
//Build list as set of labels
for (int i = 0; i < count; i++) {
final String result = searchResult.get(i);
//label with graphic (text flow) to highlight founded subtext in suggestions
Label entryLabel = new Label();
entryLabel.setGraphic(Styles.buildTextFlow(result, searchReauest));
entryLabel.setPrefHeight(10); //don't sure why it's changed with "graphic"
CustomMenuItem item = new CustomMenuItem(entryLabel, true);
menuItems.add(item);
//if any suggestion is select set it into text and close popup
item.setOnAction(actionEvent -> {
setText(result);
positionCaret(result.length());
entriesPopup.hide();
});
}
//"Refresh" context menu
entriesPopup.getItems().clear();
entriesPopup.getItems().addAll(menuItems);
}
/**
* Get the existing set of autocomplete entries.
*
* @return The existing autocomplete entries.
*/
public SortedSet<String> getEntries() { return entries; }
}
您必须从“TextField”而不是“TextFieldWithLengthLimit”扩展,并删除具有“长度限制”的构造函数。
我使用静态方法来处理样式。它在这里用于“突出显示”建议结果中输入的文本。这是此类方法的代码:
/**
* Build TextFlow with selected text. Return "case" dependent.
*
* @param text - string with text
* @param filter - string to select in text
* @return - TextFlow
*/
public 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);
}
您可以在 FXML 中(不要忘记“导入”)或构造函数内添加此“AutocompletionlTextField”。要设置使用“条目”getter 的“建议”列表:
AutocompletionlTextField field = new AutocompletionlTextField();
field.getEntries().addAll(YOUR_ARRAY_OF_STRINGS);
希望有帮助。
您可以使用 ControlsFX --> maven
解决方案:
TextFields.bindAutoCompletion(textfield,"text to suggest", "another text to suggest");
JFoenix还有另一种解决方案。自 2018 年 2 月起,他们添加了自动完成类。这就是它的实现。
// when initializing the window or in some other method
void initialize() {
JFXAutoCompletePopup<String> autoCompletePopup = new JFXAutoCompletePopup<>();
autoCompletePopup.getSuggestions().addAll("option1", "option2", "...");
autoCompletePopup.setSelectionHandler(event -> {
textField.setText(event.getObject());
// you can do other actions here when text completed
});
// filtering options
textField.textProperty().addListener(observable -> {
autoCompletePopup.filter(string -> string.toLowerCase().contains(textField.getText().toLowerCase()));
if (autoCompletePopup.getFilteredSuggestions().isEmpty() || textField.getText().isEmpty()) {
autoCompletePopup.hide();
// if you remove textField.getText.isEmpty() when text field is empty it suggests all options
// so you can choose
} else {
autoCompletePopup.show(textField);
}
});
}
这是一种有点新的方法,对我来说效果很好。希望它能有所帮助,并感谢 JFoenix 开发人员。
此示例https://gist.github.com/floralvikings/10290131仅允许字符串。
我对其进行了编辑以允许任何对象并仅使用该对象的
toString
方法。这允许您使用选定的对象执行其他操作,而不仅仅是填充文本字段。还修复了如果删除不属于条目一部分的 String
中的 TextField
会发生异常的错误。
使用示例:
SortedSet<Address> entries = new TreeSet<>((Address o1, Address o2) -> o1.toString().compareTo(o2.toString()));
entries.add(new Address("50", "Main Street", "Oakville", "Ontario", "T6P4K9"));
entries.add(new Address("3", "Fuller Road", "Toronto", "Ontario", "B6S4T9"));
AutoCompleteTextField<Address> text = new AutoCompleteTextField(entries);
text.getEntryMenu().setOnAction(e ->
{
((MenuItem) e.getTarget()).addEventHandler(Event.ANY, event ->
{
if (text.getLastSelectedObject() != null)
{
text.setText(text.getLastSelectedObject().toString());
System.out.println(text.getLastSelectedObject().getProvince());
}
});
});
自动完成文本字段.java
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.TextField;
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.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
/**
* This class is a TextField which implements an "autocomplete" functionality,
* based on a supplied list of entries.<p>
*
* If the entered text matches a part of any of the supplied entries these are
* going to be displayed in a popup. Further the matching part of the entry is
* going to be displayed in a special style, defined by
* {@link #textOccurenceStyle textOccurenceStyle}. The maximum number of
* displayed entries in the popup is defined by
* {@link #maxEntries maxEntries}.<br>
* By default the pattern matching is not case-sensitive. This behaviour is
* defined by the {@link #caseSensitive caseSensitive}
* .<p>
*
* The AutoCompleteTextField also has a List of
* {@link #filteredEntries filteredEntries} that is equal to the search results
* if search results are not empty, or {@link #filteredEntries filteredEntries}
* is equal to {@link #entries entries} otherwise. If
* {@link #popupHidden popupHidden} is set to true no popup is going to be
* shown. This list can be used to bind all entries to another node (a ListView
* for example) in the following way:
* <pre>
* <code>
* AutoCompleteTextField auto = new AutoCompleteTextField(entries);
* auto.setPopupHidden(true);
* SimpleListProperty filteredEntries = new SimpleListProperty(auto.getFilteredEntries());
* listView.itemsProperty().bind(filteredEntries);
* </code>
* </pre>
*
* @author Caleb Brinkman
* @author Fabian Ochmann
* @param <S>
*/
public class AutoCompleteTextField<S> extends TextField
{
private final ObjectProperty<S> lastSelectedItem = new SimpleObjectProperty<>();
/**
* The existing autocomplete entries.
*/
private final SortedSet<S> entries;
/**
* The set of filtered entries:<br>
* Equal to the search results if search results are not empty, equal to
* {@link #entries entries} otherwise.
*/
private ObservableList<S> filteredEntries
= FXCollections.observableArrayList();
/**
* The popup used to select an entry.
*/
private ContextMenu entriesPopup;
/**
* Indicates whether the search is case sensitive or not. <br>
* Default: false
*/
private boolean caseSensitive = false;
/**
* Indicates whether the Popup should be hidden or displayed. Use this if
* you want to filter an existing list/set (for example values of a
* {@link javafx.scene.control.ListView ListView}). Do this by binding
* {@link #getFilteredEntries() getFilteredEntries()} to the list/set.
*/
private boolean popupHidden = false;
/**
* The CSS style that should be applied on the parts in the popup that match
* the entered text. <br>
* Default: "-fx-font-weight: bold; -fx-fill: red;"
* <p>
* Note: This style is going to be applied on an
* {@link javafx.scene.text.Text Text} instance. See the <i>JavaFX CSS
* Reference Guide</i> for available CSS Propeties.
*/
private String textOccurenceStyle = "-fx-font-weight: bold; "
+ "-fx-fill: red;";
/**
* The maximum Number of entries displayed in the popup.<br>
* Default: 10
*/
private int maxEntries = 10;
/**
* Construct a new AutoCompleteTextField.
*
* @param entrySet
*/
public AutoCompleteTextField(SortedSet<S> entrySet)
{
super();
this.entries = (entrySet == null ? new TreeSet<>() : entrySet);
this.filteredEntries.addAll(entries);
entriesPopup = new ContextMenu();
textProperty().addListener((ObservableValue<? extends String> observableValue, String s, String s2) ->
{
if (getText() == null || getText().length() == 0)
{
filteredEntries.clear();
filteredEntries.addAll(entries);
entriesPopup.hide();
} else
{
LinkedList<S> searchResult = new LinkedList<>();
//Check if the entered Text is part of some entry
String text1 = getText();
Pattern pattern;
if (isCaseSensitive())
{
pattern = Pattern.compile(".*" + text1 + ".*");
} else
{
pattern = Pattern.compile(".*" + text1 + ".*", Pattern.CASE_INSENSITIVE);
}
for (S entry : entries)
{
Matcher matcher = pattern.matcher(entry.toString());
if (matcher.matches())
{
searchResult.add(entry);
}
}
if (!entries.isEmpty())
{
filteredEntries.clear();
filteredEntries.addAll(searchResult);
//Only show popup if not in filter mode
if (!isPopupHidden())
{
populatePopup(searchResult, text1);
if (!entriesPopup.isShowing())
{
entriesPopup.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
}
}
} else
{
entriesPopup.hide();
}
}
});
focusedProperty().addListener((ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean aBoolean2) ->
{
entriesPopup.hide();
});
}
/**
* Get the existing set of autocomplete entries.
*
* @return The existing autocomplete entries.
*/
public SortedSet<S> getEntries()
{
return entries;
}
/**
* 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<S> searchResult, String text)
{
List<CustomMenuItem> menuItems = new LinkedList<>();
int count = Math.min(searchResult.size(), getMaxEntries());
for (int i = 0; i < count; i++)
{
final String result = searchResult.get(i).toString();
final S itemObject = searchResult.get(i);
int occurence;
if (isCaseSensitive())
{
occurence = result.indexOf(text);
} else
{
occurence = result.toLowerCase().indexOf(text.toLowerCase());
}
if (occurence < 0)
{
continue;
}
//Part before occurence (might be empty)
Text pre = new Text(result.substring(0, occurence));
//Part of (first) occurence
Text in = new Text(result.substring(occurence, occurence + text.length()));
in.setStyle(getTextOccurenceStyle());
//Part after occurence
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) ->
{
lastSelectedItem.set(itemObject);
entriesPopup.hide();
});
menuItems.add(item);
}
entriesPopup.getItems().clear();
entriesPopup.getItems().addAll(menuItems);
}
public S getLastSelectedObject()
{
return lastSelectedItem.get();
}
public ContextMenu getEntryMenu()
{
return entriesPopup;
}
public boolean isCaseSensitive()
{
return caseSensitive;
}
public String getTextOccurenceStyle()
{
return textOccurenceStyle;
}
public void setCaseSensitive(boolean caseSensitive)
{
this.caseSensitive = caseSensitive;
}
public void setTextOccurenceStyle(String textOccurenceStyle)
{
this.textOccurenceStyle = textOccurenceStyle;
}
public boolean isPopupHidden()
{
return popupHidden;
}
public void setPopupHidden(boolean popupHidden)
{
this.popupHidden = popupHidden;
}
public ObservableList<S> getFilteredEntries()
{
return filteredEntries;
}
public int getMaxEntries()
{
return maxEntries;
}
public void setMaxEntries(int maxEntries)
{
this.maxEntries = maxEntries;
}
}
这是我的解决方案 - 仅带有 ComboBox 参数的完整方法:
/**
* My own autocomplete combobox
*
* @param categoryComboBox
*/
public static void bindAutoCompleteToComboBox(ComboBox<String> categoryComboBox) {
/**
* backup the original list
*/
List<String> categoryComboBoxItemsList = new ArrayList<String>(categoryComboBox.getItems());
/**
* if mouse pressed: select all of the text field
*/
categoryComboBox.getEditor().setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Platform.runLater(new Runnable() {
@Override
public void run() {
if (categoryComboBox.getEditor().isFocused() && !categoryComboBox.getEditor().getText().isEmpty()) {
categoryComboBox.getEditor().selectAll();
}
}
});
}
});
/**
* events on text input
*/
categoryComboBox.setOnKeyReleased(new EventHandler<KeyEvent>() {
private List<String> reducedList = new ArrayList<String>();
@Override
public void handle(KeyEvent event) {
if (event.getCode().isLetterKey() || event.getCode().isDigitKey() || event.getCode().equals(KeyCode.BACK_SPACE)) {
/**
* Open comboBox if letter, number or backspace
*/
categoryComboBox.show();
String temp = categoryComboBox.getEditor().getText();
reducedList = new ArrayList<String>();
/**
* If backspace pressed, selection refers to the basic list again
*/
if (event.getCode().equals(KeyCode.BACK_SPACE)) {
categoryComboBox.getItems().clear();
categoryComboBox.getItems().addAll(categoryComboBoxItemsList);
// java fx workaround to restore the default list height of 10
categoryComboBox.hide();
categoryComboBox.setVisibleRowCount(10);
categoryComboBox.show();
}
/**
* loop through all entrys and look whether input contains this text.
*
* after that, entry will be added to the reduced list
*/
for (String element : categoryComboBox.getItems()) {
if (StringUtils.containsIgnoreCase(element, temp)) {
reducedList.add(element);
}
}
/**
* all elements are cleared, the reduced list will be added. First element is selected
*/
categoryComboBox.getItems().clear();
categoryComboBox.getItems().addAll(reducedList);
categoryComboBox.getSelectionModel().select(0);
categoryComboBox.getEditor().setText(temp);
} else if (event.getCode().equals(KeyCode.ENTER)) {
/**
* if enter, the element which is selected will be applied to the text field and the dropdown will be closed
*/
if (categoryComboBox.getSelectionModel().getSelectedIndex() != -1) {
categoryComboBox.getEditor().setText(categoryComboBox.getItems().get((categoryComboBox.getSelectionModel().getSelectedIndex())));
} else {
categoryComboBox.getEditor().setText(categoryComboBox.getItems().get(0));
}
} else if (event.getCode().equals(KeyCode.DOWN)) {
/**
* arrow down shows the dropdown
*/
categoryComboBox.show();
}
/**
* Tab marks everything (when tabbing into the field
*/
if (event.getCode().equals(KeyCode.TAB)) {
Platform.runLater(new Runnable() {
@Override
public void run() {
if (categoryComboBox.getEditor().isFocused() && !categoryComboBox.getEditor().getText().isEmpty()) {
categoryComboBox.getEditor().selectAll();
}
}
});
} else {
/**
* all entries except for tab put the caret on the last character
*/
Platform.runLater(new Runnable() {
@Override
public void run() {
categoryComboBox.getEditor().positionCaret(categoryComboBox.getEditor().getText().length());
}
});
}
}
});
/**
* focus lost
*/
categoryComboBox.focusedProperty().addListener(new ChangeListener<Boolean>() {
/**
* if focus lost: refill the category combo box with the original items
*/
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (oldValue) {
/**
* saves whether textfield was empty before reset the comboBox
*/
boolean emptyTextField = categoryComboBox.getEditor().getText().isEmpty();
if (categoryComboBox.getSelectionModel().getSelectedIndex() != -1) {
categoryComboBox.getEditor().setText(categoryComboBox.getItems().get(categoryComboBox.getSelectionModel().getSelectedIndex()));
}
String temp = categoryComboBox.getEditor().getText();
categoryComboBox.getItems().clear();
categoryComboBox.getItems().addAll(categoryComboBoxItemsList);
if (!emptyTextField) {
categoryComboBox.getSelectionModel().select(temp);
} else {
categoryComboBox.getEditor().setText("");
}
}
}
});
}
这是一个相当古老的话题,但由于我刚刚遇到这个问题,所以我组装了已经提到的部分的解决方案。 就像@RicherdK已经说过的,你可以使用controlsfx,最简单的绑定方法就像他说的:
TextFields.bindAutoCompletion(textfield,"text to suggest", "another text to suggest");
在他的帖子的评论中提到,这对于自动完成来说相当慢。这是事实,因为在不敲击键盘的情况下需要 250 毫秒才能显示建议。因此,您可以将延迟设置为您想要的任何值:
TextFields.bindAutoCompletion(textfield,"text to suggest", "another text to suggest").setDelay(50);
除了这个小细节之外,我在使用这个库时遇到了问题,我必须添加:
--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED
我的虚拟机选项。其他一些人也遇到了类似的问题,您可以在here找到它并确保您的JavaFx版本匹配
Trilogy的答案修改(抱歉格式问题,求你使用IDE的自动格式来修复我的混乱)
有什么不同?
在构造函数上:
@param1 queryProcessor
:将当前文本公开给
允许用户提供项目(硬编码:按 CTRL+空格键来触发
它)
@param2 toString
:转换从
查询要与文本内部匹配的字符串
@param3 onSelected
:公开所选项目的函数
由用户使用的弹出窗口
当 contextMenu 显示时,默认情况下第一个项目将获得焦点,必须进行一些 CSS 检查并
Platform.runlater
以确保第一个项目被选中。
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import javafx.application.Platform;
import javafx.beans.binding.ListExpression;
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.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class AutoCompleteTextField<S> extends TextField {
final int maxEntries = 8;
final Function<S, String> toString;
final ContextMenu entriesPopup = new ContextMenu();
final Function<String, ListExpression<S>> queryProcessor;
final Consumer<S> onSelected;
String textOccurenceStyle = "-fx-font-weight: bold; " + "-fx-fill: red;";
ListExpression<S> entries;
public AutoCompleteTextField(
Function<String, ListExpression<S>> queryProcessor,
Function<S, String> toString, Consumer<S> onSelected
) {
this.toString = toString;
this.queryProcessor = queryProcessor;
this.onSelected = onSelected;
this.entries = queryProcessor.apply("");
textProperty().addListener((observableValue, s, text) -> {
if (text == null || text.length() == 0) {
entriesPopup.hide();
return;
}
processText(text);
});
addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.isShortcutDown() && e .getCode()
.equals(KeyCode.SPACE)) {
e.consume();
entriesPopup.hide();
var text = getText();
this.entries = queryProcessor.apply(text);
clear();
setText(text);
positionCaret(text.length());
processText(text);
}
});
focusedProperty().addListener((obs, old, focus) -> {
entriesPopup.hide();
});
entriesPopup.showingProperty()
.addListener((obs, old, showing) -> {
if (entriesPopup.getItems()
.size() > 0) {
Platform.runLater(()->{
var item = entriesPopup .getSkin()
.getNode()
.lookup(".menu-item");
if (!item.isFocused()) {
item.requestFocus();
this.fireEvent(
new KeyEvent(
KeyEvent.KEY_PRESSED,
"",
"",
KeyCode.DOWN,
false,
false,
false,
false
)
);
}
});
}
});
}
void processText(String text) {
var searchResult = new LinkedList<S>();
var pattern = Pattern.compile(".*" + text + ".*", Pattern.CASE_INSENSITIVE);
for (S entry : entries) {
var matcher = pattern.matcher(toString.apply(entry));
if (matcher.matches()) {
searchResult.add(entry);
}
}
if (!entries.isEmpty()) {
populatePopup(searchResult, text);
if (entriesPopup.getItems()
.size() > 0) {
entriesPopup.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
} else {
entriesPopup.hide();
}
} else {
entriesPopup.hide();
}
}
private void populatePopup(List<S> searchResult, String text) {
entriesPopup.getItems()
.clear();
int count = Math.min(searchResult.size(), maxEntries);
for (int i = 0; i < count; i++) {
final String result = toString.apply(searchResult.get(i));
final S itemObject = searchResult.get(i);
int occurence = result.toLowerCase()
.indexOf(text.toLowerCase());
if (occurence < 0) {
continue;
}
Text matched = new Text(result.substring(occurence, occurence + text.length()));
matched.setStyle(textOccurenceStyle);
TextFlow entryFlow = new TextFlow(
new Text(result.substring(0, occurence)),
matched,
new Text(result.substring(occurence + text.length(), result.length()))
);
CustomMenuItem item = new CustomMenuItem(
entryFlow,
true
);
item.setOnAction((ActionEvent actionEvent) -> {
onSelected.accept(itemObject);
setText(toString.apply(itemObject));
selectAll();
entriesPopup.hide();
});
entriesPopup.getItems()
.add(item);
}
if (entriesPopup.getItems()
.size() > 1) {
entriesPopup.hide();
}
}
}
应用示例:
import javafx.application.Application;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class AppTest extends Application{
static class Item {
final StringProperty name = new SimpleStringProperty();
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this .nameProperty()
.get();
}
public final void setName(final String name) {
this.nameProperty()
.set(name);
}
}
public ListProperty<Item> getItems(String query) {
var items = new SimpleListProperty<Item>(FXCollections.observableArrayList());
// simulate query for new matches
for (var i = 0; i < 10; i++) {
var item = new Item();
// item.setName(query + i); //new matches found
item.setName("item" + i);// no new matches found
items.add(item);
}
return items;
}
@Override
public void start(Stage primaryStage) throws Exception {
VBox v = new VBox();
var itemtextfield = new AutoCompleteTextField<>(
this::getItems,
Item::getName,
System.out::println
);
v .getChildren()
.add(itemtextfield);
primaryStage.setScene(new Scene(v));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
如果您不想使用任何外部库(例如controlsfx),这是我的解决方案:
以下是 JavaFX 控制器类中的代码示例:
public class AutocompleteController {
@FXML
private TextField textField;
@FXML
private ListView<String> suggestionsList;
private final ObservableList<String> suggestions = FXCollections.observableArrayList();
private final StringProperty text = new SimpleStringProperty();
public void initialize() {
text.bind(textField.textProperty());
text.addListener((observable, oldValue, newValue) -> updateSuggestions(newValue));
suggestionsList.setItems(suggestions);
suggestionsList.setOnMouseClicked(event -> {
String selectedSuggestion = suggestionsList.getSelectionModel().getSelectedItem();
if (selectedSuggestion != null) {
textField.setText(selectedSuggestion);
suggestions.clear();
}
});
}
private void updateSuggestions(String enteredText) {
// Clear the previous suggestions
suggestions.clear();
// Get the new suggestions based on enteredText and add them to the suggestions list
List<String> newSuggestions = getSuggestions(enteredText);
suggestions.addAll(newSuggestions);
}
private List<String> getSuggestions(String enteredText) {
// Implement your logic to retrieve suggestions based on enteredText
// ...
return suggestions;
}
}
如果您希望建议列表视图是动态的,以便其高度取决于列表视图上的项目,请创建一个 DoubleBinding,根据列表中的项目数量和固定单元格大小来计算 ListView 的高度。然后将绑定应用于 ListView 的 prefHeight 属性以使高度动态化。
这是一个例子:
suggestionsList.setFixedCellSize(24.0); // Set a fixed height for each cell
DoubleBinding heightBinding = new DoubleBinding() {
{
super.bind(suggestionsList.getItems());
}
@Override
protected double computeValue() {
return suggestionsList.getFixedCellSize() * suggestionsList.getItems().size();
}
};
suggestionsList.prefHeightProperty().bind(heightBinding);