4

我想使用 TextFlow 显示一些格式化的文本。以前,我使用一个简单的标签(将 wrapText 设置为 true)来显示该文本(未格式化),但想使用一个库,该库提供了一个我想使用 TextFlow 显示的文本列表。

我的问题是我要显示的文本大于可用区域。当空间不足时,标签会切断文本。这很好用。不幸的是,TextFlow 没有。当文本变得太长时,它会溢出 TextFlow 所在的区域。相邻的 TextFlows 然后相互重叠。如何模仿标签的行为?

可以在此处和下方找到 MWE。我使用带有两列的 GridPane。左边三个 TextFlow,右边三个 Label。所有六个元素的显示文本都是相同的。它产生这个窗口:

在此处输入图像描述

如您所见,左侧的文本(在 TextFlows 中)重叠。

我试过了,没有成功:

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

爪哇:

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

FXML:

<?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>
4

1 回答 1

5

由于似乎没有内置的方法可以做到这一点,我实现了自己的。这可能不是解决这个问题的最有效方法,但很好地满足了我的用例。如果在接下来的几天里出现更好的解决方案,我会接受其中一个。如果没有,我会选择这个答案作为接受。

在此处输入图像描述

还有一个问题:我需要单击一次窗口才能使文本在开头显示。此外,还有一个主要问题:如果子节点不是 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;
    }
}
于 2021-07-27T15:01:49.647 回答