我在我的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中导航时,只有当用户应用两次 Select (仅在最上面和最下面)时, Select 才变得可见,尽管每次都会调用selectRange().

这是为什么?这是JavaFX库中的一个错误,还是我遗漏了什么?

我已经搜索了这个问题几次,但还没有找到任何结果. 我还没有使用AWT Swing测试这一点,但我非常有信心,使用它会工作得很好.

我的Java是带有JavaFX版本19的OpenJDK 19.

推荐答案

将低级事件处理(如鼠标和键盘事件处理程序)添加到高级UI控件从来都不是一个特别好的主意.您总是面临干扰控件的内置功能的风险,或者内置功能干扰事件处理程序导致无法提供您想要的功能的风险.

在您的例子中,后一种情况正在发生:当按下键时,TextArea已经实现了更改 Select .如果在没有修改键的情况下按下箭头键,则 Select 将被清除.此默认行为正在发生after您的按键被调用,因此默认行为是您所看到的行为.

您可以通过添加以下内容查看正在发生的情况

textArea.selectionProperty().addListener((obs, oldSel, newSel) -> 
    System.out.printf("Selection change: %s -> %s%n", oldSel, newSel)
);

注您可以通过将event.consume()添加到键事件处理程序中的两个if() { ... }块来解决此问题,但我不认为这是一个特别好的解决方案:下面的解决方案更好.

看起来您只是在这里使用了错误的控件.如果您有一个不可编辑的TextArea,其中您将每一行都视为一个不同的项目,那么听起来您似乎正在try 重新实现ListView,它已经实现了 Select 各个项目之类的事情.您可以使用以下命令重新实现您的示例:

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)));
        }
    }

}

Java相关问答推荐

将具有多个未知字段的SON映射到Java POJO

gitlab ci不会运行我的脚本,因为它需要数据库连接'

当切换javaFX场景时,stage的大小正在Minimize

强制Mockito返回null而不是emtpy list

Springdoc Whitelabel Error Page with Spring V3

如何在Java中声明未使用的变量?

错误:在Liferay7.4中找不到符号导入com.liferay.portal.kernel.uuid.PortalUUID;";

Java中是否有某种类型的池可以避免重复最近的算术运算?

当我已经安装了其他版本的Java时,如何在Mac OSX 14.3.1上安装Java 6?

使用Mockito进行的Junit测试失败

如何在JavaFX中处理多个按钮

如何在Spring Boot中创建可以将值传递给配置的&Enable&Quot;注释?

S数学.exp的相同错误保证也适用于StrictMath.exp吗?

如何使用Java对随机生成的字母数字优惠券代码进行过期设置

Kotlin-仅替换字符串中最后一个给定的字符串

为什么我的登录终结点不能被任何请求访问?

如何使用Rascal Evaluator从编译的JAR访问Rascal函数?

如何正确使用java.time类?

OpenJDK20:JEP434:Foreign Function&;内存API(第二次预览)

Java方法参数:括号中的类型声明?