所以我正在编写一个AutosuggestMenu
,为TextField
添加一个监听器,并在弹出窗口ContextMenu
中推荐建议,基于将输入的击键与Collection
进行比较提供的词语。
遗憾的是,ContextMenu
元素的更改未显示,我怀疑这是因为我正在修改与ObservableList
关联的ContextMenu
元素,而不是列表本身。
浏览堆栈导致我相信我应该实现extractor,但根据提供的示例,我不知道如何针对我的具体问题执行此操作。任何解决方案都将非常感谢!
来源:
package com.sknb.gui;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class AutosuggestMenu {
public final static int DEFAULT_MENU_SIZE = 10;
//Data members
private int menuSize;
private Collection<String> wordList;
//GUI members
private ContextMenu menu;
private final Text textBefore, textMatching, textAfter;
public AutosuggestMenu(Collection<String> keyList) {
this(keyList, DEFAULT_MENU_SIZE);
}
public AutosuggestMenu(Collection<String> keyList, int numEntries) {
if (keyList == null) {
throw new NullPointerException();
}
this.wordList = keyList;
this.menuSize = numEntries;
this.menu = new ContextMenu();
for (int i = 0; i < this.menuSize; i++) {
CustomMenuItem item = new CustomMenuItem(new Label(), true);
this.menu.getItems().add(item);
}
this.textBefore = new Text();
this.textMatching = new Text();
this.textAfter = new Text();
}
public void addListener(TextField field) {
field.textProperty().addListener((observable, oldValue, newValue) -> {
String enteredText = field.getText();
if (enteredText == null || enteredText.isEmpty()) {
this.menu.hide();
} else {
List<String> filteredEntries = this.wordList.stream()
.filter(e -> e.contains(enteredText))
.collect(Collectors.toList());
if (!filteredEntries.isEmpty()) {
populatePopup(field, filteredEntries, enteredText);
if (!(this.menu.isShowing())) {
this.menu.show(field, Side.BOTTOM, 0, 0);
}
} else {
this.menu.hide();
}
}
});
field.focusedProperty().addListener((observableValue, oldValue, newValue) -> {
this.menu.hide();
});
}
private void populatePopup(TextField field, List<String> matches, String query) {
int i = 0,
max = (matches.size() > this.menuSize) ? this.menuSize :
matches.size();
for (MenuItem item : this.menu.getItems()) {
if (i < max) {
String result = matches.get(i);
item.setGraphic(generateTextFlow(result, query));
item.setVisible(true);
item.setOnAction(actionEvent -> {
field.setText(result);
field.positionCaret(result.length());
this.menu.hide();
});
} else {
item.setVisible(false);
}
i++;
}
}
private TextFlow generateTextFlow(String text, String filter) {
int filterIndex = text.indexOf(filter);
this.textBefore.setText(text.substring(0, filterIndex));
this.textAfter.setText(text.substring(filterIndex + filter.length()));
this.textMatching.setText(text.substring(filterIndex, filterIndex + filter.length()));
textMatching.setFill(Color.BLUE);
textMatching.setFont(Font.font("Helvetica", FontWeight.BOLD, 12));
return new TextFlow(textBefore, textMatching, textAfter);
}
public int getMenuSize() {
return this.menuSize;
}
public void setMenuSize(int size) {
this.menuSize = size;
}
public Collection<String> getKeyList() {
return this.wordList;
}
public void setKeyList(Collection<String> keyList) {
this.wordList = keyList;
}
//To do: add ways to change style of ContextMenu/menu items/text elements
}
使用text file of English words作为字典测试类:
package autosuggestfieldtest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import com.sknb.gui.AutosuggestMenu;
public class AutosuggestFieldTest extends Application {
private TextField textfield;
private Collection<String> words;
private AutosuggestMenu popup;
@Override
public void start(Stage primaryStage) {
String filename = Paths.get("").toAbsolutePath().toString() + "\\words.txt";
try (Stream<String> stream = Files.lines(Paths.get(filename))) {
words = stream
.collect(Collectors.toSet());
} catch (IOException e) {
System.out.println("DERP");
}
popup = new AutosuggestMenu(words);
textfield = new TextField();
popup.addListener(textfield);
StackPane root = new StackPane();
root.getChildren().add(textfield);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("AutocompleteField Test");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
注意:这是similar solution Ruslan的修改,但我想知道是否有一个解决方案不涉及每次按键清除/重新填充菜单?即只需使用setGraphic
并刷新ContextMenu
?