为什么 JavaFX TextArea 中的 selectRange() 有时不突出显示所选内容?

问题描述 投票:0回答:1

我在 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。

java javafx textarea selection
1个回答
0
投票

向高级 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))); } } }
    
© www.soinside.com 2019 - 2024. All rights reserved.