JavaFX: как изменить цвет текста в TextArea, соответствующий selectRange

Я планирую получить диапазон выделенного текста из TextArea и изменить цвет шрифта selectRange. Моя TextArea инициализируется как часть файла FXML. Я могу получить selectRange с помощью textArea.getSelection(), я хотел бы раскрасить текст в диапазоне, возвращаемом вышеуказанным методом. Пожалуйста, предложите мне, как это сделать.


person Karthik SP    schedule 28.11.2018    source источник
comment
Не слишком ясно, вы просто хотите, чтобы TextArea отображал другой цвет при выборе текста, или вы хотите отформатировать выделенный текст так, чтобы он имел другой цвет?   -  person Jai    schedule 28.11.2018
comment
Я хотел бы отформатировать выделенный текст, чтобы он имел другой цвет. Извиняюсь за неясность. Я новичок в JavaFX.   -  person Karthik SP    schedule 28.11.2018
comment
Я не уверен, что вы действительно поняли, что я имел в виду. Первый означает, что вы пытаетесь изменить внешний вид файла TextArea. С другой стороны, последнее означает, что сам текст будет форматированным текстом, и это форматирование останется, когда вы скопируете его в другой текстовый редактор. Также для последнего станет возможным иметь разные цвета в разных сегментах всего текста.   -  person Jai    schedule 28.11.2018
comment
Хорошо, я постараюсь быть более точным. Я пытаюсь создать приложение для выделения текста. Все выделенные строки из моего TextArea должны быть окрашены, скажем, желтым шрифтом. Таким образом, даже после того, как я удалю выделение, ранее выделенный текст должен иметь цветовой формат, чтобы указать, что это выделенный текст.   -  person Karthik SP    schedule 28.11.2018


Ответы (1)


Во-первых, я бы рекомендовал использовать любые элементы управления форматированным текстом (RichTextFX) для вашего требования. Но если вы очень специфичны в использовании TextArea, проверьте приведенный ниже подход.

Подход, который я предлагаю, может быть неправильным решением или не соответствовать вашим конкретным требованиям. Но это может помочь вам думать в этом направлении.

Это основано на идее, представленной (от @en_Knight) в выделения строк в JavaFX TextArea . Я рекомендую сначала пройти этот OP, а затем проверить мою реализацию ниже.

Я создал собственный элемент управления TextArea, который работает по тому же принципу, что и в упомянутом OP. т. е. смешивание узлов для получения желаемых эффектов. Вы можете проверить различные эффекты наложения, изменив значение поля со списком в демоверсии. Я предполагаю, что для вашего требования вам может понадобиться BlendMode.ADD

Выделение по-прежнему остается, пока вы выделяете текст. введите здесь описание изображения

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.BlendMode;
import javafx.scene.layout.*;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.stage.Stage;

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

public class CustomTextAreaDemo extends Application {
    @Override
    public void start(Stage stage) throws Exception {

        final VBox root = new VBox();
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        final Scene sc = new Scene(root, 600, 300);
        stage.setScene(sc);
        stage.show();

        final CustomTextArea customTextArea = new CustomTextArea();
        customTextArea.setText(
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
        customTextArea.setWrapText(true);
        customTextArea.setStyle("-fx-font-size: 15px;");
        VBox.setVgrow(customTextArea, Priority.ALWAYS);

        final Button highlight = new Button("Highlight");
        final TextField stF = new TextField("40");
        final TextField enF = new TextField("51");
        final HBox hb = new HBox(highlight, stF, enF);
        hb.setSpacing(10);
        highlight.setOnAction(e -> {
            customTextArea.highlight(Integer.parseInt(stF.getText()), Integer.parseInt(enF.getText()));
        });

        final Button remove = new Button("Remove Highlight");
        remove.setOnAction(e -> customTextArea.removeHighlight());

        final Label lbl = new Label("Resize the window to see if the highlight is in align with text");
        lbl.setStyle("-fx-font-size: 17px;-fx-font-style:italic;");
        final HBox rb = new HBox(remove, lbl);
        rb.setSpacing(10);

        ComboBox<BlendMode> blendModes = new ComboBox<>();
        blendModes.getItems().addAll(BlendMode.values());
        blendModes.getSelectionModel().selectedItemProperty().addListener((obs, old, blend) -> {
            customTextArea.getHighlightPath().setBlendMode(blend);
        });
        blendModes.getSelectionModel().select(BlendMode.ADD);

        root.getChildren().addAll(hb, rb,blendModes, customTextArea);
    }

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

    class CustomTextArea extends TextArea {
        /**
         * Start position of the highlight.
         */
        private int highlightStartPos = -1;

        /**
         * End position of the highlight.
         */
        private int highlightEndPos = -1;

        /**
         * Path node to act as highlight.
         */
        private final Path highlightPath = new Path();

        /**
         * Node to keep reference of the selectionGroup node of the TextArea.
         */
        private Group selectionGroup;

        /**
         * Node to keep reference of the all contents of the TextArea.
         */
        private StackPane contentPane;

        /**
         * Node to keep reference of the content node.
         */
        private Region textContent;

        /**
         * Specifies whether the selection of text is done for purpose of highlighting.
         */
        private boolean highlightInProgress = false;

        public CustomTextArea() {
            highlightPath.setStyle("-fx-fill:red");
            highlightPath.setMouseTransparent(true);
            highlightPath.setManaged(false);
            highlightPath.setStroke(null);
            textProperty().addListener((obs, oldVal, newVal) -> removeHighlight());
            widthProperty().addListener((obs, oldVal, newVal) -> {
                if (highlightStartPos > -1 && highlightEndPos > -1 && selectionGroup != null) {
                    highlightInProgress = true;
                    selectRange(highlightStartPos, highlightEndPos);
                }
            });
        }

        /**
         * Highlights the range of characters in the text area.
         *
         * @param startPos Start position of the character in the text
         * @param endPos   End position of the character in the text
         */
        public void highlight(final int startPos, final int endPos) {
            highlightStartPos = startPos;
            highlightEndPos = endPos;
            highlightInProgress = true;
            selectRange(highlightStartPos, highlightEndPos);
        }

        public Path getHighlightPath() {
            return highlightPath;
        }

        @Override
        protected void layoutChildren() {
            super.layoutChildren();
            if (selectionGroup == null || contentPane == null) {
                final Region content1 = (Region) lookup(".content");
                if (content1 != null) {
                    // Looking for the Group node that is responsible for selection
                    content1
                            .getChildrenUnmodifiable()
                            .stream()
                            .filter(node -> node instanceof Group)
                            .map(node -> (Group) node)
                            .filter(grp -> {
                                final boolean notSelectionGroup =
                                        grp.getChildren().stream().anyMatch(node -> !(node instanceof Path));
                                return !notSelectionGroup;
                            })
                            .findFirst()
                            .ifPresent(n -> {
                                n.boundsInLocalProperty().addListener((obs, old, bil) -> {
                                    if (highlightInProgress) {
                                        updateHightlightBounds();
                                    }
                                });
                                selectionGroup = n;
                            });
                    contentPane = (StackPane) content1.getParent();
                    textContent = content1;
                }
            }
        }

        /**
         * Updates the highlight with the provided bounds.
         */
        private void updateHightlightBounds() {
            if (!selectionGroup.getChildren().isEmpty()) {
                final Path p = (Path) selectionGroup.getChildren().get(0);
                final List<PathElement> elements = new ArrayList<>(p.getElements());

                highlightPath.getElements().clear();
                highlightPath.getElements().addAll(elements);
                final Node textNode = textContent.lookup(".text");
                highlightPath.setLayoutX(textNode.getLayoutX());
                highlightPath.setLayoutY(textNode.getLayoutY());

                if (contentPane != null && !contentPane.getChildren().contains(highlightPath)) {
                    contentPane.getChildren().add(highlightPath);
                }
                highlightInProgress = false;
                Platform.runLater(this::deselect);
            }
        }

        /**
         * Removes the highlight in the text area.
         */
        public void removeHighlight() {
            if (contentPane != null) {
                contentPane.getChildren().remove(highlightPath);
            }
            highlightStartPos = -1;
            highlightEndPos = -1;
        }
    }
}
person Sai Dandem    schedule 28.11.2018