问题
I'm trying to get the current word or partial word currently being typed in a JavaFX TextArea to pass to to a very basic AutoComplete getPredictions(String prefix); method.
Right now I am using an Event Handler on KeyTyped and I'm going to track each character typed and concatenate it with the previously typed word, resetting on a space. This is going to have a lot of pitfalls I can see with AutoComplete being turned on in the middle of typing a word, getting the previous word if backspace is hit and the caret is at the end of the word again.
I'm curious to see if there's an alternative approach anyone can think of where I simply get the String for the word from the caret position back to the last space using TextInputControl methods. I haven't got it working my way yet but I'll post what I have at this point.
I basically need to get the current word being typed preserving the current caret position as the use types.
public AutoSpellingTextArea() {
// register and handle KEY_TYPED event
this.addEventHandler(KeyEvent.KEY_TYPED,new EventHandler<KeyEvent>(){
public void handle(KeyEvent t) {
// BUG -- turning on autocomplete in middle of word.
if(autoCompleteOn) {
//TODO
String pre = getCurrentPrefix(t.getCharacter());
if(pre != null) {
List<String> choices = ac.predictCompletions(pre, NUM_COMPLETIONS);
}
}
}
});
}
private String getCurrentPrefix(String ch) {
String retVal = null;
// space entered
if(ch.equals(" ")) {
lastWord = currentPrefix;
currentPrefix = "";
return retVal;
}
// add next character to prefix string
currentPrefix = currentPrefix + ch;
return retVal;
}
回答1:
Maybe you can get what you need just be observing the caratPositionProperty and backtracking to the last whitespace character:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class AutoCompleteTextAreaTest extends Application {
@Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
Label currentWord = new Label();
textArea.caretPositionProperty().addListener((obs, oldPosition, newPosition) -> {
String text = textArea.getText().substring(0, newPosition.intValue());
int index ;
for (index = text.length() - 1; index >= 0 && ! Character.isWhitespace(text.charAt(index)); index--);
String prefix = text.substring(index+1, text.length());
currentWord.setText(prefix);
});
BorderPane root = new BorderPane(textArea, currentWord, null, null, null);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You could extend this idea to search forward from the carat position too, so that if the cursor is in the middle of a word, you can identify all possible words that could result from an insertion at that point.
Here's an SSCCE demonstrating this, using a random word list I found online:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class AutoCompleteTextAreaTest extends Application {
private static final String WORD_LIST_URL = "https://raw.githubusercontent.com/dwyl/english-words/master/words.txt?raw=true";
@Override
public void start(Stage primaryStage) {
StackPane loadingRoot = new StackPane(new ProgressBar());
Scene scene = new Scene(loadingRoot, 600, 600);
List<String> words = new ArrayList<>();
Task<List<String>> loadWordsTask = new Task<List<String>>() {
@Override
public List<String> call() throws Exception {
try (BufferedReader in = new BufferedReader(new InputStreamReader(new URL(WORD_LIST_URL)
.openConnection()
.getInputStream()))) {
return in.lines()
.collect(Collectors.toList());
}
}
};
ListView<String> suggestions = new ListView<>();
TextArea textArea = new TextArea();
textArea.caretPositionProperty().addListener((obs, oldPosition, newPosition) -> {
String text = textArea.getText().substring(0, newPosition.intValue());
int index ;
for (index = text.length() - 1;
index >= 0 && ! Character.isWhitespace(text.charAt(index));
index--);
String prefix = text.substring(index+1, text.length());
for (index = newPosition.intValue();
index < textArea.getLength() && ! Character.isWhitespace(textArea.getText().charAt(index));
index++);
String suffix = textArea.getText().substring(newPosition.intValue(), index);
// replace regex wildcards (literal ".") with "\.". Looks weird but correct...
prefix = prefix.replaceAll("\\.", "\\.");
suffix = suffix.replaceAll("\\.", "\\.");
Pattern pattern = Pattern.compile(prefix+".*"+suffix,
Pattern.CASE_INSENSITIVE);
suggestions.getItems().setAll(
words.stream().filter(word -> pattern.matcher(word).matches())
.sorted(Comparator.comparing(String::length))
.limit(100)
.collect(Collectors.toList())
);
});
BorderPane root = new BorderPane(textArea, null, suggestions, null, null);
loadWordsTask.setOnSucceeded(e -> {
words.addAll(loadWordsTask.getValue());
scene.setRoot(root);
});
loadWordsTask.setOnFailed(e -> {
suggestions.setPlaceholder(new Label("Could not load word list"));
loadWordsTask.getException().printStackTrace();
scene.setRoot(root);
});
Thread t = new Thread(loadWordsTask);
t.setDaemon(true);
t.start();
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
来源:https://stackoverflow.com/questions/32403650/javafx-get-current-word-being-typed-in-textarea