提问人:nick16384 提问时间:9/11/2023 更新时间:9/11/2023 访问量:50
为什么 JavaFX TextArea 中的 selectRange() 有时不突出显示所选内容?
Why does selectRange() in JavaFX TextArea sometimes not highlight the selection?
问:
我在 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");
}
}
我知道它看起来一团糟,并不完全干净,但你应该明白这一点。
现在问题如下:
当我使用箭头键(特别是 UP 和 DOWN)导航时,只有当用户应用两次(仅在最顶部和底部)时,选择才会变得可见,尽管每次都会调用 to。TextArea
selectRange()
为什么会这样?是 JavaFX 库中的错误,还是我遗漏了什么?
我已经搜索了几次这个问题,但还没有找到任何结果。 我还没有使用 AWT Swing 对此进行测试,但我非常有信心,使用它可以正常工作。
我的 Java 是带有 JavaFX 版本 19 的 OpenJDK 19。
答:
将低级事件处理(如鼠标和键盘事件处理程序)添加到高级 UI 控件从来都不是一个特别好的主意。您始终面临干扰控件的内置功能的风险,或者内置功能干扰事件处理程序的风险,从而导致无法提供所需的功能。
在您的例子中,后者正在发生:已经实现了在按下键时更改选择。如果在没有修饰键的情况下按下箭头键,则选择将被清除。此默认行为发生在调用按键之后,因此默认行为是您看到的行为。TextArea
您可以通过添加
textArea.selectionProperty().addListener((obs, oldSel, newSel) ->
System.out.printf("Selection change: %s -> %s%n", oldSel, newSel)
);
请注意,您可以通过添加键事件处理程序中的两个块来解决此问题,但我认为这不是一个特别好的解决方案:恕我直言,下面的解决方案更好。event.consume()
if() { ... }
看起来您只是在这里使用了错误的控件。如果你有一个不可编辑的,你把每一行都看作是一个不同的项目,那么听起来你正在尝试重新实现一个,它已经实现了诸如选择单个项目之类的事情。您可以使用以下方法重新实现您的示例: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();
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)));
}
}
}
评论
ListView
ListView
TextArea