javafx - 如何让 JavaFX TextFlow 自动剪切长文本?

标签 javafx textflow

我想使用 TextFlow 显示一些格式化文本。以前,我使用了一个简单的 Label(将 wrapText 设置为 true)来显示该文本(未格式化),但想要使用一个库来提供我想使用 TextFlow 显示的文本列表。
我的问题是我要显示的文本大于可用区域。当空间不足时,标签会切断文本。这很好用。不幸的是 TextFlow 没有。当文本变得太长时,它会溢出 TextFlow 所在的区域。相邻的 TextFlow 会相互重叠。我怎样才能模仿标签的行为?
可以找到 MWE here和下面。
我使用带有两列的 GridPane。左侧三个 TextFlow,右侧三个标签。所有六个元素的显示文本都相同。
它产生这个窗口:
enter image description here
如您所见,左侧(在 TextFlows 中)的文本重叠。
我试过了,没有成功:

  • 设置TextFlow的maxWidth和maxHeight为可用区域
  • 创建适当大小的矩形并将其设置为剪辑

  • java :
    package sample;
    
    import javafx.application.Application;
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.text.Text;
    import javafx.scene.text.TextFlow;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        @FXML
        private TextFlow textFlow0;
        @FXML
        private TextFlow textFlow1;
        @FXML
        private TextFlow textFlow2;
        @FXML
        private Label label0;
        @FXML
        private Label label1;
        @FXML
        private Label label2;
    
        private String longText = "This is some really long text that should overflow the available Area. " +
                "For TextFields, this is handeled by cropping the text to appropriate length and adding \"...\" at the end. " +
                "No such option exists for TextFlows";
    
        @Override
        public void start(Stage primaryStage) throws Exception{
            Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
            primaryStage.setTitle("Text Overflow");
            primaryStage.setScene(new Scene(root, 300, 275));
            primaryStage.show();
        }
    
        @FXML
        private void initialize() {
            textFlow0.getChildren().add(new Text(longText));
            textFlow1.getChildren().add(new Text(longText));
            textFlow2.getChildren().add(new Text(longText));
            label0.setText(longText);
            label1.setText(longText);
            label2.setText(longText);
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    文件格式:
    <?import javafx.scene.control.Label?>
    <GridPane fx:controller="sample.Main"
              xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
        <TextFlow fx:id="textFlow0" GridPane.rowIndex = "0" GridPane.columnIndex="0" />
        <Label fx:id="label0" GridPane.rowIndex = "0" wrapText="true" GridPane.columnIndex="1"/>
        <TextFlow fx:id="textFlow1" GridPane.rowIndex = "1" GridPane.columnIndex="0" />
        <Label fx:id="label1" GridPane.rowIndex = "1" wrapText="true" GridPane.columnIndex="1"/>
        <TextFlow fx:id="textFlow2" GridPane.rowIndex = "2" GridPane.columnIndex="0" />
        <Label fx:id="label2" GridPane.rowIndex = "2" wrapText="true" GridPane.columnIndex="1"/>
    </GridPane>
    
    失败:尝试使用剪辑
    package sample;
    
    import javafx.application.Application;
    import javafx.fxml.FXML;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.layout.FlowPane;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.text.Text;
    import javafx.scene.text.TextFlow;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        @FXML
        private FlowPane flowPane;
    
        @FXML
        private TextFlow textFlow0;
        @FXML
        private TextFlow textFlow1;
        @FXML
        private TextFlow textFlow2;
        @FXML
        private Label label0;
        @FXML
        private Label label1;
        @FXML
        private Label label2;
    
        private String longText = "This is some really long text that should overflow the available Area. " +
                "For TextFields, this is handeled by cropping the text to appropriate length and adding \"...\" at the end. " +
                "No such option exists for TextFlows";
    
        @Override
        public void start(Stage primaryStage) throws Exception{
            Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
            primaryStage.setTitle("Text Overflow");
            primaryStage.setScene(new Scene(root, 300, 275));
            primaryStage.show();
        }
    
        @FXML
        private void initialize() {
            flowPane.setPrefWrapLength(Double.MAX_VALUE);
            Rectangle rect = new Rectangle();
            rect.widthProperty().bind(flowPane.widthProperty());
            rect.heightProperty().bind(flowPane.heightProperty());
            flowPane.setClip(rect);
            textFlow0.getChildren().add(new Text(longText));
            textFlow1.getChildren().add(new Text(longText));
            textFlow2.getChildren().add(new Text(longText));
            label0.setText(longText);
            label1.setText(longText);
            label2.setText(longText);
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    
    用于剪辑尝试的 FXML 文件
    <?import javafx.scene.layout.GridPane?>
    <?import javafx.scene.text.TextFlow?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.FlowPane?>
    <GridPane fx:controller="sample.Main"
              xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
        <FlowPane fx:id="flowPane" GridPane.rowIndex = "0" GridPane.columnIndex="0">
            <TextFlow fx:id="textFlow0" />
        </FlowPane>
        <Label fx:id="label0" GridPane.rowIndex = "0" wrapText="true" GridPane.columnIndex="1"/>
        <TextFlow fx:id="textFlow1" GridPane.rowIndex = "1" GridPane.columnIndex="0" />
        <Label fx:id="label1" GridPane.rowIndex = "1" wrapText="true" GridPane.columnIndex="1"/>
        <TextFlow fx:id="textFlow2" GridPane.rowIndex = "2" GridPane.columnIndex="0" />
        <Label fx:id="label2" GridPane.rowIndex = "2" wrapText="true" GridPane.columnIndex="1"/>
    </GridPane>
    

    最佳答案

    由于似乎没有内置的方法可以做到这一点,我实现了自己的。
    这可能不是解决这个问题的最有效方法,但很好地满足了我的用例。
    如果接下来几天出现更好的解决方案,我会接受其中一个。如果没有,我会选择这个答案作为接受。
    enter image description here
    仍然存在一个问题:我需要单击窗口一次才能让文本显示在开头。
    此外,还有一个主要问题:如果子节点不是 Text 对象怎么办?

    package sample;
    
    import javafx.beans.DefaultProperty;
    import javafx.beans.property.StringProperty;
    import javafx.beans.value.ChangeListener;
    import javafx.collections.FXCollections;
    import javafx.collections.ListChangeListener;
    import javafx.collections.ObservableList;
    import javafx.scene.Node;
    import javafx.scene.text.Text;
    import javafx.scene.text.TextFlow;
    
    @DefaultProperty("children")
    public class EllipsingTextFlow extends TextFlow {
    
        private final static String DEFAULT_ELLIPSIS_STRING = "...";
        private StringProperty ellipsisString;
    
        //private ListProperty<Node> allChildren = new SimpleListProperty<Node>(new SimpleObs<Node>());
        private ObservableList<Node> allChildren = FXCollections.observableArrayList();
        private ChangeListener sizeChangeListener = (observableValue, number, t1) -> adjustText();
    
        public EllipsingTextFlow() {
            allChildren.addListener((ListChangeListener<Node>) this::adjustChildren);
            widthProperty().addListener(sizeChangeListener);
            heightProperty().addListener(sizeChangeListener);
            adjustText();
        }
    
        @Override
        public ObservableList<Node> getChildren() {
            return allChildren;
        }
    
        private void adjustChildren(ListChangeListener.Change<? extends Node> change) {
            while (change.next()) {
                if (change.wasRemoved()) {
                    super.getChildren().remove(change.getFrom(), change.getTo());
                } else if (change.wasAdded()) {
                    super.getChildren().addAll(change.getFrom(), change.getAddedSubList());
                }
            }
            adjustText();
        }
    
        private void adjustText() {
            // remove listeners
            widthProperty().removeListener(sizeChangeListener);
            heightProperty().removeListener(sizeChangeListener);
            while (getHeight() > getMaxHeight() || getWidth() > getMaxWidth()) {
                if (super.getChildren().isEmpty()) {
                    // nothing fits
                    widthProperty().addListener(sizeChangeListener);
                    heightProperty().addListener(sizeChangeListener);
                    return;
                }
                super.getChildren().remove(super.getChildren().size()-1);
                super.autosize();
            }
            while (getHeight() <= getMaxHeight() && getWidth() <= getMaxWidth()) {
                if (super.getChildren().size() == allChildren.size()) {
                    if (allChildren.size() > 0) {
                        // all Texts are displayed, let's make sure all chars are as well
                        Node lastChildAsShown = super.getChildren().get(super.getChildren().size() - 1);
                        Node lastChild = allChildren.get(allChildren.size() - 1);
                        if (lastChildAsShown instanceof Text && ((Text) lastChildAsShown).getText().length() < ((Text) lastChild).getText().length()) {
                            ((Text) lastChildAsShown).setText(((Text) lastChild).getText());
                        } else {
                            // nothing to fill the space with
                            widthProperty().addListener(sizeChangeListener);
                            heightProperty().addListener(sizeChangeListener);
                            return;
                        }
                    }
                } else {
                    super.getChildren().add(allChildren.get(super.getChildren().size()));
                }
                super.autosize();
            }
            // ellipse the last text as much as necessary
            while (getHeight() > getMaxHeight() || getWidth() > getMaxWidth()) {
                Node lastChildAsShown = super.getChildren().remove(super.getChildren().size()-1);
                while (getEllipsisString().equals(((Text) lastChildAsShown).getText())) {
                    if (super.getChildren().size() == 0) {
                        widthProperty().addListener(sizeChangeListener);
                        heightProperty().addListener(sizeChangeListener);
                        return;
                    }
                    lastChildAsShown = super.getChildren().remove(super.getChildren().size() -1);
                }
                if (lastChildAsShown instanceof Text && ((Text) lastChildAsShown).getText().length() > 0) {
                    // Text shortenedChild = new Text(((Text) lastChildAsShown).getText().substring(0, ((Text) lastChildAsShown).getText().length()-1));
                    Text shortenedChild = new Text(ellipseString(((Text) lastChildAsShown).getText()));
                    super.getChildren().add(shortenedChild);
                } else {
                    // don't know what to do with anything else. Leave without adding listeners
                    return;
                }
                super.autosize();
            }
            widthProperty().addListener(sizeChangeListener);
            heightProperty().addListener(sizeChangeListener);
        }
    
        private String ellipseString(String s) {
            int spacePos = s.lastIndexOf(' ');
            if (spacePos < 0) {
                return getEllipsisString();
            }
            return s.substring(0, spacePos) + getEllipsisString();
        }
    
        public final void setEllipsisString(String value) {
            ellipsisString.set((value == null) ? "" : value);
        }
    
        public String getEllipsisString() {
            return ellipsisString == null ? DEFAULT_ELLIPSIS_STRING : ellipsisString.get();
        }
    
        public final StringProperty ellipsisStringProperty(){
            return ellipsisString;
        }
    }
    

    关于javafx - 如何让 JavaFX TextFlow 自动剪切长文本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68527631/

    相关文章:

    binding - 如何绑定(bind)两个不同的 JavaFx 属性 : String and Double (with StringConverter)?

    java - JavaFX 中的 TextFlow 不显示任何内容

    java - 如何为 TableView 创建条件 CellValueFactory?

    java - 在单个 hbox 中为 2 个元素提供单独的对齐方式

    JavaFX:从 DoubleProperty 的 ObservableSet 获取值的运行总计

    apache-flex - 在 Flex 中设置 RichText 元素符号的样式

    layout - JavaFX - 在 TextFlow 中垂直居中文本

    没有换行符的 JavaFX TextFlow

    popup - 有没有一种简单的方法可以在 JavaFX 中显示提示文本?