我在 JavaFX 主类中编写了以下代码:
package jfxTest;
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
private static int selectionIndex = 0;
private static TextArea textArea;
@Override
public void start(Stage primaryStage) {
System.out.println("Starting JavaFX Window...");
StackPane rootPane = new StackPane();
textArea = new TextArea();
textArea.setText("TEST");
textArea.setEditable(false);
createRandomOptions(8);
textArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode().equals(KeyCode.UP)) {
select(selectionIndex <= 0 ? 0 : selectionIndex - 1);
}
if (event.getCode().equals(KeyCode.DOWN)) {
String[] lines = textArea.getText().split("\n");
select(selectionIndex >= lines.length - 1 ? lines.length - 1 : selectionIndex + 1);
}
});
rootPane.getChildren().add(textArea);
Scene scene = new Scene(rootPane, 900, 500);
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("Created window.");
System.out.println("\nNow press up and down keys to navigate:\n"
+ "Notice, that although a new selection is displayed in the console, it is not\n"
+ "highlighted in the window itself. Even a call to getSelectedText() works fine.\n");
}
/**
* Creates a bunch of random options in textArea.
* @param newOptionCount
*/
private static void createRandomOptions(int newOptionCount) {
for (int i = 0; i < newOptionCount; i++) {
textArea.appendText("\nSEL" + (new Random().nextInt(10000)));
}
}
private static void select(int newSelectionIndex) {
String[] lines = textArea.getText().split("\n");
System.out.println("New selection index: " + newSelectionIndex);
selectionIndex = newSelectionIndex;
// Determine selection indexes
int selectionStart = 0;
int selectionEnd = 0;
for (int i = 0; i < newSelectionIndex; i++) {
selectionStart += lines[i].length() + 1;
}
selectionEnd = selectionStart + lines[newSelectionIndex].length();
// Does not work. Selection does need to be applied twice by the user to be actually highlighted.
textArea.selectRange(selectionStart, selectionEnd);
System.out.println("Selected text: " + textArea.getSelectedText() + "\n");
}
}
我知道它看起来很乱而且不太干净,但你应该明白我的意思。 现在问题如下: 当我使用箭头键(特别是向上和向下)在
TextArea
中导航时,只有当用户应用两次(仅在最顶部和底部)时,选择才变得可见,尽管调用 selectRange()
每次都会制作。
这是为什么呢?这是 JavaFX 库中的错误,还是我遗漏了什么?
这个问题我已经搜索过几次了,但还没有找到结果。 我还没有使用 AWT Swing 对此进行测试,但我非常有信心,使用它可以很好地工作。
我的 Java 是 OpenJDK 19,JavaFX 版本为 19。
向高级 UI 控件添加低级事件处理(例如鼠标和键盘事件处理程序)从来都不是一个特别好的主意。您始终面临着干扰控件内置功能的风险,或者内置功能干扰您的事件处理程序,从而导致无法提供您想要的功能的风险。
在您的情况下,后者正在发生:
TextArea
已经实现了按下按键时更改选择。如果按下箭头键而没有修饰键,则选择将被清除。此默认行为是在调用按键后发生的,因此默认行为就是您看到的行为。
您可以通过添加来查看发生了什么
textArea.selectionProperty().addListener((obs, oldSel, newSel) ->
System.out.printf("Selection change: %s -> %s%n", oldSel, newSel)
);
看起来您只是在这里使用了错误的控件。如果您有一个不可编辑的 TextArea
,其中您将每一行视为不同的项目,那么听起来您正在尝试重新实现
ListView
,它已经实现了诸如选择单个项目之类的功能。您可以简单地重新实现您的示例:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
private ListView<String> listView;
@Override
public void start(Stage primaryStage) {
System.out.println("Starting JavaFX Window...");
StackPane rootPane = new StackPane();
listView = new ListView<>();
listView.getItems().add("TEST");
createRandomOptions(8);
rootPane.getChildren().add(listView);
Scene scene = new Scene(rootPane, 900, 500);
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("Created window.");
System.out.println("\nNow press up and down keys to navigate:\n"
+ "Notice, that although a new selection is displayed in the console, it is not\n"
+ "highlighted in the window itself. Even a call to getSelectedText() works fine.\n");
}
/**
* Creates a bunch of random options in textArea.
* @param newOptionCount
*/
private void createRandomOptions(int newOptionCount) {
for (int i = 0; i < newOptionCount; i++) {
listView.getItems().add("SEL" + (new Random().nextInt(10000)));
}
}
}
如果您确实想修改文本区域中的选定内容,请使用 TextFormatter
和可修改选择的过滤器。这看起来像这样(不过,我再次认为这只是重新发明轮子)。请注意,这也符合您(大概)想要的鼠标点击方式:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.IndexRange;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
private TextArea textArea;
@Override
public void start(Stage primaryStage) {
System.out.println("Starting JavaFX Window...");
StackPane rootPane = new StackPane();
textArea = new TextArea();
textArea.selectionProperty().addListener((obs, oldSel, newSel) -> System.out.printf("Selection change: %s -> %s%n", oldSel, newSel));
textArea.setText("TEST");
textArea.setEditable(false);
createRandomOptions(8);
textArea.setTextFormatter(new TextFormatter<String>(this::modifySelection));
rootPane.getChildren().add(textArea);
Scene scene = new Scene(rootPane, 900, 500);
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("Created window.");
System.out.println("\nNow press up and down keys to navigate:\n"
+ "Notice, that although a new selection is displayed in the console, it is not\n"
+ "highlighted in the window itself. Even a call to getSelectedText() works fine.\n");
}
private TextFormatter.Change modifySelection(TextFormatter.Change change) {
IndexRange selection = change.getSelection();
String[] lines = change.getControlNewText().split("\n");
int lineStart = 0 ;
for (String line : lines) {
int lineEnd = lineStart + line.length();
System.out.printf("Line: %d %d %s%n", lineStart, lineEnd, line);
if (lineStart <= selection.getStart() && lineEnd >= selection.getStart()) {
change.setAnchor(lineStart);
}
if (lineStart <= selection.getEnd() && lineEnd >= selection.getEnd()) {
change.setCaretPosition(lineEnd);
}
lineStart += line.length() + 1; // +1 to account for line terminator itself
}
return change;
}
/**
* Creates a bunch of random options in textArea.
* @param newOptionCount
*/
private void createRandomOptions(int newOptionCount) {
for (int i = 0; i < newOptionCount; i++) {
textArea.appendText("\nSEL" + (new Random().nextInt(10000)));
}
}
}