Textflow inside Tablecell: not correct cell height - javafx

I wont to place formated text inside a JavaFX table. First I was trying to embedd a Webview but I had problems with cell heigh. This seems to be a mission feature in JavafX (see: Java FX: TableView - display simple HTML).
Based on the recommendation here I tried to embed a TextFlow. However again the sizing of the TabelCell is not correct. I want the height of the row just as big, that the content of the cell fits inside. Changeing the column width should result in changeing the row heigth.
Here is my minimal running example.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
public class TableViewSample extends Application {
private final ObservableList<MyData> data = FXCollections.observableArrayList(new MyData(1L), new MyData(3L), new MyData(2L), new MyData(4L), new MyData(1L));
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage stage) {
final Scene scene = new Scene(new Group());
TableView<MyData> table = new TableView<>();
final TableColumn<MyData, Long> nameCol = new TableColumn("So So");
nameCol.setMinWidth(200);
nameCol.setCellValueFactory(new PropertyValueFactory<>("i"));
// Allow to display Textflow in Column
nameCol.setCellFactory(column -> {
return new TableCell<MyData, Long>() {
#Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setStyle("");
} else {
// Generate Textflow with variable length
TextFlow textFlow = new TextFlow();
textFlow.setPrefHeight(Region.USE_COMPUTED_SIZE);
for (Long ii = 1L; ii <= item; ii++) {
Text text1 = new Text("la la la ");
text1.setFill(Color.RED);
Text text2 = new Text("blue blue blue ");
text2.setFill(Color.BLUE);
textFlow.getChildren().add(text1);
textFlow.getChildren().add(text2);
}
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(textFlow);
// setPrefHeight(20); // <- Shows the expected effect.
// However I want to have variable height of row.
setPrefHeight(Region.USE_COMPUTED_SIZE); // <- Why is that not working
}
}
};
});
table.setItems(data);
table.getColumns().addAll(nameCol);
((Group) scene.getRoot()).getChildren().addAll(table);
stage.setScene(scene);
stage.show();
}
public static class MyData {
private Long i;
public MyData(Long i) { this.i = i; }
public Long getI() { return i; }
}
}
As you can see the row height is way to big. Any Suggestions?

I had the same issue within an application and thought it is a bug in the FX code. After debugging the JDK code I finally understood where the problem is coming from.
During rendering the TextFlow will be asked for its preferred height given a width of -1. The TextFlowLayout will then return the height as if it would have to display everything in an extremely narrow column, thus resulting in a preferred height direct proportional to the number of characters in your TextFlow object. Because the height of a row is not bounded in a TableView or TreeTableView the table will allocate the preferred height of the TextFlow object resulting in the strange appearance as above.
The problem can be solved by setting the preferred height of the cell to the height of the textFlow and additionally adding a ChangeListner to the width to adapt the height if the the width of the cell changes.
setPrefHeight(textFlow.prefHeight(nameCol.getWidth()) + 4);
nameCol.widthProperty().addListener((v, o, n) ->
setPrefHeight(textFlow.prefHeight(n.doubleValue()) + 4));

After a lot of time trying to resolve this issue I found a simple solution.
Bind the flowPane width to the column width and then put it in a Group and set the group as the graphic.
So try:
//Bind the max width of textFlow
//(6 is the sum of the default left and right margins)
textFlow.maxWidthProperty().bind(getTableColumn().widthProperty().subtract(6));
setGraphic(new Group(textFlow));

TableView increase cell height for each text node in cell. If you use a TextFlow component, row increases the own height as if each letter were in a separate row.
image
I dont khow how to prevent this bug.
I can advise you to use only one Text node in cell, or instead of a TableView use a ListView. This component is free of this shortcoming.

Related

JavaFX ScrollPane - Detect when scrollbar is visible?

I have a ScrollPane as below:
ScrollPane scroller = new ScrollPane();
scroller.getStyleClass().add("scroller");
scroller.setPrefWidth(width);
scroller.setFocusTraversable(Boolean.FALSE);
scroller.setPannable(Boolean.TRUE);
scroller.setFitToWidth(Boolean.TRUE);
scroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroller.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
this.setCenter(scroller);
scroller.contentProperty().addListener((observableValue, last, now) ->
{
ScrollBar scrollBar = (ScrollBar) scroller.lookup(".scroll-bar:vertical");
if (scrollBar != null)
{
if (scrollBar.isVisible())
{
log.info("Scrollbar visible, setting lower card width..");
}
else
{
log.info("Scrollbar not visible, setting default card width..");
}
}
});
As you can see I've attached a listener to the content property to know when the content is set. I am trying to see if the scrollbar is visible when the content is updated. Even though I can see the scroll bar on the UI, it always goes to else part - "Scrollbar not visible".
Not sure if there is any other way to do this? Checked a lot on StackOverflow and Oracle docs - nothing solid found to suggest otherwise.
-- Adding context to the problem to better understand:
Just trying to explain what the problem is not sure if I should put it as a reply comment or edit the question, please advise and will change it:
So I have this view that brings up records from Firebase that need to be loaded on the TilePane that is hosted in ScrollPane which goes into the Center of the BorderPane.
The time by which I get the response from the Firebase is unpredictable as its async. So the UI gets loaded up with the empty TilePane and then the async call goes to fetch data. When the data is available, I need to prepare Cards (which is HBox) but the number of columns is fixed. So have to adjust the width of the cards to keep the gap (16px) and padding (16px) consistent on the TilePane at the same time maintain 5 columns. The width of each card needs to be recalculated based on the fact that whether or not there is a scrollbar on the display. Because if the scrollbar is displayed it takes some space and the TilePane will down it to 4 columns leaving a lot of empty space. Happy to explain further if this is not clear.
I strongly suggest to follow the suggestions given in the comments. It is all about choosing the correct layout.
The purpose of me answering this question is, in future, if someone comes across this question for dealing with scroll bar visibility, they will atleast know a way to get that (in JavaFX 8).
One way to check for the scrollbar visiblity is to register the appropriate scrollbar on layoutChildren and add a listener to its visilble property. Something like...
ScrollPane scrollPane = new ScrollPane() {
ScrollBar vertical;
#Override
protected void layoutChildren() {
super.layoutChildren();
if (vertical == null) {
vertical = (ScrollBar) lookup(".scroll-bar:vertical");
vertical.visibleProperty().addListener((obs, old, val) -> updateContent(val));
updateContent(vertical.isVisible());
}
}
};
The updateContent(visible) method is stuff you want to do when the visibility gets updated.
A complete working demo is as below.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ScrollPaneScrollBarVisibility_Demo extends Application {
#Override
public void start(Stage stage) throws Exception {
BorderPane borderPane = new BorderPane();
Scene sc = new Scene(borderPane, 300, 300);
stage.setScene(sc);
stage.setTitle("ScrollBar visibility");
stage.show();
ScrollPane scrollPane = new ScrollPane() {
ScrollBar vertical;
#Override
protected void layoutChildren() {
super.layoutChildren();
if (vertical == null) {
vertical = (ScrollBar) lookup(".scroll-bar:vertical");
vertical.visibleProperty().addListener((obs, old, val) -> updateContent(val));
updateContent(vertical.isVisible());
}
}
};
scrollPane.setContent(getContent());
borderPane.setCenter(scrollPane);
}
private void updateContent(boolean scrollBarVisible) {
System.out.println("Vertical scroll bar visible :: " + scrollBarVisible);
}
private VBox getContent() {
VBox labels = new VBox();
labels.setSpacing(5);
for (int i = 0; i < 10; i++) {
labels.getChildren().add(new Label("X " + i));
}
Button add = new Button("Add");
add.setOnAction(e -> labels.getChildren().add(new Label("Text")));
Button remove = new Button("Remove");
remove.setOnAction(e -> {
if (!labels.getChildren().isEmpty()) {
labels.getChildren().remove(labels.getChildren().size() - 1);
}
});
HBox buttons = new HBox(add, remove);
buttons.setSpacing(15);
VBox content = new VBox(buttons, labels);
content.setPadding(new Insets(15));
content.setSpacing(15);
return content;
}
public static void main(String[] args) {
Application.launch(args);
}
}
As #James_D said, used GridPane and it worked without any listeners:
GridPane cards = new GridPane();
cards.setVgap(16);
cards.setHgap(16);
cards.setAlignment(Pos.CENTER);
cards.setPadding(new Insets(16));
ColumnConstraints constraints = new ColumnConstraints();
constraints.setPercentWidth(20);
constraints.setHgrow(Priority.ALWAYS);
constraints.setFillWidth(Boolean.TRUE);
cards.getColumnConstraints().addAll(constraints, constraints, constraints, constraints, constraints);
I have 5 columns, so 5 times constraints. Worked just fine.

JavaFX, Universal size for TextArea dependent upon content as "Message Bubble"

Thank you ahead of time for your time taken.
Currently, I am in the process of creating a JavaFX GUI for a simple-enough client/server application.
On the right side of a SplitPane is a GridPane, where-by every time a message is sent or received, that Message is displayed within the new ROW in the GridPane, and the message is basically an ImageView(image) followed by a TextArea with a String in it displaying the message sent/received.
My issue is that I cannot figure out after over a week how to size the TextArea appropriately for the block of text within it.
Before you mark this question as a duplicate, I have tried every implementation I could find.
Firstly, the ScrollBar listening solution does not work on runtime, this only appears to work WHILE a user is typing, so I have scratched that as a potential solution for my particular issue.
The solution I'm currently using (which isn't working) is using a Text object and getting the layout bounds/height of THAT for the TextArea.
I am fine with my TextAreas (acting as message bubbles) all being the same width, as of now I am specifying the minWidth to be 300.0, the problem again is with the HEIGHT.
My code is as follows:
HBox messageBox = new HBox(10);
messageBox.setPadding(new Insets(0, 0, 0, 25));
TextArea textArea = new TextArea();
textArea.setText(message);
textArea.setFont(new Font(20));
textArea.setWrapText(true);
final Text helper = new Text();
helper.setText(message);
helper.setFont(textArea.getFont());
helper.setWrappingWidth(300.0);
double width = helper.getLayoutBounds().getWidth();
double height = helper.getLayoutBounds().getHeight();
textArea.setMinWidth(width);
textArea.setPrefHeight(height);
messageBox.getChildren().addAll(imageView, textArea);
messagePane.add(messageBox, 0, rowCount);
rowCount++;
Please note that I have also tried placing my helper Text object into a throw-away Pane, which renders almost identical results.
Lastly, I have tried adding padding to the setPrefHeight() of the TextArea, I have tried MinHeight/MaxHeight combinations.
This picture illustrates my FIRST problem, the 3rd message has far too much space below the end of the block of text, while preceding message look fine, (IMO). The second picture BELOW demonstrated my 2nd problem, larger blocks of text seem to gradually decrease the width of the TextAreas or perhaps the HBox's above them. Before these subsequent HBox's were, added, the highlighted TextArea had enough space, for instance.
Is there any solution that will work for my needs?
I would be very grateful, thank you for your time!
Keith
This is not a trivial task (unless you find a workaround), I am afraid you will have to somehow to compute the actual width and height and apply it to the TextArea. The way I am thinking is to either find your magic numbers by trial and error approach or better take the message text add it to a Label and then compute its dimensions (width, height) and then use those in order to set the TextArea. Here is a small example :
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class MessagerTest extends Application {
private VBox displayPane = new VBox(5);
private TextArea messageArea = new TextArea();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
BorderPane mainPane = new BorderPane();
ScrollPane scrollPane = new ScrollPane(displayPane);
displayPane.setPadding(new Insets(10));
displayPane.prefWidthProperty().bind(scrollPane.widthProperty());
scrollPane.prefWidthProperty().bind(mainPane.widthProperty());
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
mainPane.setCenter(scrollPane);
mainPane.setBottom(messageArea);
mainPane.setPadding(new Insets(10));
messageArea.setPrefHeight(120);
messageArea.setFont(Font.font(16));
messageArea.setWrapText(true);
messageArea.setPromptText("Type a message here...");
messageArea.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ENTER && !e.isShiftDown()) {
sendMessage(messageArea.getText());
e.consume();
} else if (e.getCode() == KeyCode.ENTER && e.isShiftDown()) {
messageArea.appendText(System.lineSeparator());
}
});
mainPane.getStylesheets().add(this.getClass().getResource("messanger.css").toExternalForm());
stage.setScene(new Scene(mainPane, 450, 600));
stage.show();
}
private void sendMessage(String message) {
TextArea txtArea = new TextArea(message);
txtArea.setWrapText(true);
txtArea.setId("Message");
txtArea.setEditable(true);
resizeTextArea(txtArea);
displayPane.getChildren().add(txtArea);
messageArea.clear();
}
private void resizeTextArea(TextArea txtArea) {
String text = txtArea.getText();
double maxWidth = displayPane.getWidth() - 40;
HBox h = new HBox();
Label l = new Label(text);
l.setFont(Font.font(15));
h.getChildren().add(l);
Scene s = new Scene(h);
l.impl_processCSS(true);
l.applyCss();
double width = l.prefWidth(-1) + 20;
double height = l.prefHeight(-1) + 20;
if (width > maxWidth) {
txtArea.setMaxWidth(maxWidth);
txtArea.setMinWidth(maxWidth);
} else {
txtArea.setMaxWidth(width);
txtArea.setMinWidth(width);
}
txtArea.setMinHeight(height);
txtArea.setMaxHeight(height);
}
}
In case you want the CSS file too :
#Message {
-fx-background-color : transparent;
-fx-font-size : 15px;
-fx-text-fill: black;
-fx-display-caret:false;
}
#Message .content:pressed {
-fx-background-color: #E5E4E4;
}
#Message .content {
-fx-background-color: #F1F0F0;
}
.scroll-pane > .viewport {
-fx-background-color: white;
}
The problem with the above is that when you write everything is one line and let the TextArea wrap the text this cause the actual label height to be bigger so you will have to adjust the values a bit in that case.
To be honest I am not sure if this is the only approach you can take or if its the optimal solution. I believe its worth to lose the mouse selection of the text and use a Label instead of doing the above with the TextArea.

JavaFX TextArea Limit

How do I set a limit on text area. I already made a counter that keeps track of the amount of characters in the text area, now I just need something to put in my if statement to make it impossible to put anymore text in the text area. How do I do that?
There's no point in creating a counter: the number of characters in the text area is already always available just from textArea.getText().length(), or, if you need an observable value, Bindings.length(textArea.textProperty()).
To limit the number of characters in a text area, set a TextFormatter which uses a filter that vetoes changes to the text if they would cause the text to exceed the maximum:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.stage.Stage;
public class LimitedTextArea extends Application {
#Override
public void start(Stage primaryStage) {
final int MAX_CHARS = 15 ;
TextArea textArea = new TextArea();
textArea.setTextFormatter(new TextFormatter<String>(change ->
change.getControlNewText().length() <= MAX_CHARS ? change : null));
Scene scene = new Scene(textArea, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

javafx 8 listview first and last row

Is there a way to determine the first and last visible row of a listview? In other words I'm looking for two indexes into an array that populates a listview which represent the top and the bottom row of the 'display window'.
You could get the VirtualFlow of the ListView which has methods for getting the first and last rows.
Example:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.skin.VirtualFlow;
public class ListViewSample extends Application {
#Override
public void start(Stage stage) {
VBox box = new VBox();
ListView<Integer> list = new ListView<>();
ObservableList<Integer> items = FXCollections.observableArrayList();
for( int i=0; i < 100; i++) {
items.add(i);
}
list.setItems(items);
box.getChildren().add(list);
VBox.setVgrow(list, Priority.ALWAYS);
Scene scene = new Scene(box, 200, 200);
stage.setScene(scene);
stage.show();
VirtualFlow flow = (VirtualFlow) list.lookup( ".virtual-flow");
flow.addEventFilter(Event.ANY, event -> {
IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
IndexedCell last = flow.getLastVisibleCellWithinViewPort();
System.out.println( list.getItems().get( first.getIndex()) + " - " + list.getItems().get( last.getIndex()) );
});
}
public static void main(String[] args) {
launch(args);
}
}
You see the fully visible first and last items in the console.
ps: I leave the no data check and event handling to you
Alternate version without css lookup:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.skin.VirtualFlow;
public class ListViewSample extends Application {
ListView<String> list = new ListView<String>();
#Override
public void start(Stage stage) {
VBox box = new VBox();
ListView<Integer> list = new ListView<>();
ObservableList<Integer> items = FXCollections.observableArrayList();
for( int i=0; i < 100; i++) {
items.add(i);
}
list.setItems(items);
box.getChildren().add(list);
VBox.setVgrow(list, Priority.ALWAYS);
Scene scene = new Scene(box, 200, 200);
stage.setScene(scene);
stage.show();
VirtualFlow virtualFlow = null;
for( Node node: list.getChildrenUnmodifiable()) {
if( node instanceof VirtualFlow) {
virtualFlow = (VirtualFlow) node;
}
}
final VirtualFlow flow = virtualFlow;
flow.addEventFilter(Event.ANY, event -> {
IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
IndexedCell last = flow.getLastVisibleCellWithinViewPort();
System.out.println( list.getItems().get( first.getIndex()) + " - " + list.getItems().get( last.getIndex()) );
});
}
public static void main(String[] args) {
launch(args);
}
}
UPDATE
VirtualFlow is available only after the ListView has been rendered, because it uses Layout parameters which are not available until after the ListView is visible on the stage. So I had to make sure that I got the VirtualFlow when it was certain that the ListView had been rendered. Since I was manipulating the list with various methods I call this method at the end of each method:
private VirtualFlow flow;
private void updateListView(int centreIndex) {
if (flow == null)
flow = (VirtualFlow) myListView.lookup(".virtual-flow");
if (flow != null){
IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
IndexedCell last = flow.getLastVisibleCellWithinViewPort();
System.out.println(first.getIndex() + " - " + last.getIndex());
}
// Now the list can be selectively 'redrawn' using the scollTo() method,
// and using the .getSelectionModel().select(centreIndex) to set the
// desired cell
}
It's bit of a hack, but it works. Using layout parameters does have a drawback though that needs to be considered. If the height of the ListView is only 1 pixel less than the total height of all rows, n number of rows will be visible, but the flow will report n-1 rows which will appear to be a discrepancy at first. Hence keeping a fixed layout height is imperative. At least now by using scrollTo(..) I have control over the position of the selected item in the list (I want to keep it centred in the list display when an item is dragged through the list). This solution leaves me feeling uneasy, but it seems to be the only 'simple' way.
Just a note on the odd-looking logic. It seems that getting the flow takes time, while the program keeps executing. The second (flow != null) is necessary to avoid a NullPointerException.
UPDATE 2
My hack turns out not to work. The whole hack is dependent on timing. Rendering is done on a different thread and as soon as I changed the order of instantiation of classes in my app, I got a NullPointerException again. I turned to the Java doc:
"JavaFX is not thread safe and all JavaFX manipulation should be run on the JavaFX processing thread. If you allow a JavaFX application to interact with a thread other than the main processing thread, unpredictable errors will occur"
And they do! So forget the above - it does not work and will make you scratch your head (and more!) trying to debug it ;-)

JavaFX: optimal width of TextFlow with word-wrap

With Textflow.setMaxWidth(double) I can achieve text wrapping.
But how can I adjust the width of TextFlow afterwards so that it is based on the actual wrapping position?
In other words, how to let the TextFlow bounds snap to all of its children Text bounds to get rid of the empty space on the right:
**Edit** I have made some progress on this issue. My class derived from TextFlow now contains:
double maxChildWidth = 0;
for (Node child : getManagedChildren()) {
double childWidth = child.getLayoutBounds().getWidth();
maxChildWidth = Math.max(maxChildWidth, childWidth);
}
double insetWidth = getInsets().getLeft() + getInsets().getRight();
double adjustedWidth = maxChildWidth + insetWidth;
setMaxWidth(adjustedWidth);
Unfortunately, this approach does not seem to be accurate yet, since it results in a second text flow change in some cases.
The solution you posted in your question seemed to work OK for me on testing.
In the image below the scene has been resized to trigger wrapping of the TextFlow (shown in red). On the right, the TextFlow does not exactly wrap at the last visible character, because the last character in the line is a space, so the wrapping occurs after the space. Taking into account the TextFlow bounds are flush to all of the text as required.
If you unclamp the max width, you get the default behavior of the text box which is to have the width grow (as you can see below).
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import javafx.stage.Stage;
public class TextFlowWrapper extends Application {
#Override
public void start(Stage stage) {
TextFlow textFlow = new TextFlow(
new Text(
"Box with long description that should be wrapped"
)
) {
#Override
protected void layoutChildren() {
super.layoutChildren();
double maxChildWidth = 0;
for (Node child : getManagedChildren()) {
double childWidth = child.getLayoutBounds().getWidth();
maxChildWidth = Math.max(maxChildWidth, childWidth);
}
double insetWidth = getInsets().getLeft() + getInsets().getRight();
double adjustedWidth = maxChildWidth + insetWidth;
setMaxWidth(adjustedWidth);
}
};
textFlow.setStyle("-fx-background-color: red");
textFlow.setMaxWidth(Control.USE_PREF_SIZE);
textFlow.setMaxHeight(Control.USE_PREF_SIZE);
Button unclamp = new Button("Unclamp max width");
unclamp.setOnAction(e -> textFlow.setMaxWidth(Double.MAX_VALUE));
StackPane wrappedText = new StackPane(textFlow);
VBox vbox = new VBox(
unclamp,
wrappedText
);
VBox.setVgrow(wrappedText, Priority.ALWAYS);
Scene scene = new Scene(
vbox
);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The solution is a little bit of a hack, you might want to ask one of the JavaFX developers for some input on the openjfx developer list. Maybe some additional api like a boolean pack property which would conditionally update the internal textflow layout algorithm to automatically pack the textflow to a minimal size would be a way to do this better. Part of the difficulty is that it is a hard question to write in such a way that the issue is easily understandable. It is also hard to say if this is some kind of corner case or if it is something that more people will encounter (hence perhaps justifying the complexity of an additional API).

Resources