javafx - 如何获取文本节点中给定坐标处的字符索引?

标签 javafx javafx-8

在 JavaFX 中,如何从鼠标单击事件中获取鼠标指针下的字符索引(在 javafx.scene.text.Text 对象中)?或者更一般地说,如何获取位于 javafx.scene.text.Text 节点中 (x, y) 坐标处的字符索引?

我已经设法使用 Node.queryAccessibleAttribute() 找到了索引,但这感觉有点像黑客而不是正确使用 JavaFX API:

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;

import static javafx.scene.AccessibleAttribute.OFFSET_AT_POINT;

public class TextClicked extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        final Text textNode = new Text("Hello, world!");
        textNode.setOnMouseClicked(event -> {
            final int idx = (int) textNode.queryAccessibleAttribute(OFFSET_AT_POINT, new Point2D(event.getScreenX(), event.getScreenY()));
            System.out.println("Character clicked: " + textNode.getText().charAt(idx));
        });

        primaryStage.setScene(new Scene(new HBox(textNode)));
        primaryStage.show();
    }
}

最佳答案

JavaFX 并不真正鼓励使用控件注册事件处理程序,然后检查事件的低级细节(例如鼠标坐标)以推断有关事件的语义信息的功能。首选方法是将监听器注册到场景图中的各个包含节点。例如,在 Swing 中,您可以使用 JList 注册一个监听器。然后 locationToIndex(...)获取 JList 中项目索引的方法, 在 JavaFX 中,鼓励您向个人 ListCell 注册监听器s 而不是 ListView本身。

因此,执行此操作的“惯用”方法可能是创建一个 TextFlow并添加个人 Text对它。确定哪个Text单击后,您将使用每个单独的文本注册听众。

你可以创建一个可重用的类来封装这个功能,当然,暴露尽可能多的封闭TextFlow的API。根据您的需要。

这是一个相当基本的例子:

import java.util.ArrayList;
import java.util.List;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.Event;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.InputEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;

public class ClickableTextExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        ClickableText text = new ClickableText("The quick brown fox jumped over the lazy dog.");
        text.asNode().addEventHandler(TextClickEvent.CLICKED, e -> {
            System.out.println("Click on "+e.getCharacter()+" at "+e.getIndex());
        });

        StackPane root = new StackPane(text.asNode());
        Scene scene = new Scene(root, 350, 120);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static class ClickableText {
        private final StringProperty text = new SimpleStringProperty();
        public StringProperty textProperty() {
            return text ;
        }
        public String getText() {
            return textProperty().get();
        }
        public void setText(String text) {
            textProperty().set(text);
        }

        private final TextFlow textFlow ;

        public ClickableText(String text) {

            textFlow = new TextFlow();

            textProperty().addListener((obs, oldText, newText) -> 
                rebuildText(newText));

            setText(text);
        }

        public Node asNode() {
            return textFlow ;
        }

        private void rebuildText(String text) {
            List<Text> textNodes = new ArrayList<>();
            for (int i = 0; i < text.toCharArray().length; i++) {
                char c = text.charAt(i);
                Text textNode = new Text(Character.toString(c));
                textNodes.add(textNode);
                registerListener(textNode, i);
            }
            textFlow.getChildren().setAll(textNodes);
        }

        private void registerListener(Text text, int index) {
            text.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
                TextClickEvent event = new TextClickEvent(text.getText().charAt(0), index, textFlow, textFlow);
                Event.fireEvent(textFlow, event);
            });
        }


    }

    public static class TextClickEvent extends InputEvent {

        private final char c ;
        private final int index ;


        public static final EventType<TextClickEvent> CLICKED = new EventType<TextClickEvent>(InputEvent.ANY);

        public TextClickEvent(char c, int index, Node source, Node target) {

            super(source, target, CLICKED);

            this.c = c ;
            this.index = index ;
        }

        public char getCharacter() {
            return c ;
        }

        public int getIndex() {
            return index ;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

另一种解决方案,您可能会或可能不会认为是一种黑客行为,它是使用不可编辑的文本字段并将其样式设置为 Text。 (或 Label)。然后你可以查看caretPosition用户点击后。

此代码基于 Copiable Label/TextField/LabeledText in JavaFX
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.TextField;
import javafx.stage.Stage;


public class TextClicked extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        final TextField textNode = new TextField("Hello, world!");
        textNode.setEditable(false);
        textNode.getStyleClass().add("clickable-text");
        textNode.setOnMouseClicked(event -> {
            final int idx = Math.max(0, textNode.getCaretPosition() - 1);
            System.out.println("Character clicked: " + textNode.getText().charAt(idx));
        });

        Scene scene = new Scene(new HBox(textNode));
        scene.getStylesheets().add("clickable-text.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

进而

clickable-text.css:
.clickable-text, .clickable-text:focused {
    -fx-background-color: transparent ;
    -fx-background-insets: 0px ;
}

关于javafx - 如何获取文本节点中给定坐标处的字符索引?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32734645/

相关文章:

layout - JavaFX 平分 child

java - ProgressBar 不与 javafx 结合

java - JavaFX 的自动完成在 eclipse luna 中不起作用

JavaFX FXML-SceneBuilder-图像可调整大小

java - 如何强制 JavaFX ComboBox 失去焦点?

执行过程时 JavaFX 不确定进度条

java - 在 Javafx 中,从数据库填充 TableView 后按钮停止工作

javafx - JavaFX中 slider 拇指的值

java - FXML 资源未加载

java - 错误或功能 : no focusOwner if control added to scene directly?