JavaFX: optimal width of TextFlow with word-wrap - javafx

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).

Related

JavaFX Improper layouting of SplitPane contents when the content has FlowPane

I am encountering an issue with SplitPane dividers if the content has multiline FlowPane. There is no issue if the FlowPane rendered in one row. If the FlowPane has more than one row then there is a shift in the content part.
The more the no of rows, the greater the shift is.
To demonstrate the issue, below is quick a demo. The demo contains three vertical splitPanes, where each SplitPane has FlowPane with different no. of rows. (1st splitPane - 1row, 2nd SplitPane - 2rows, 3rd SplitPane - 3rows)
When resizing the splitPane with 1 FlowPane row, there is no issue, everything works fine. Whereas if I resize the second splitPane, the content is shifting from its desired place leaving a void space in SplitPane. When resizing the third splitPane, the space is even much bigger.
I believe this should be some issue in SplitPane-FlowPane calculations (Or I might be wrong as well). But at this stage rather than trying to figure the root cause (which will be somewhere inside JavaFX source code), I am more desperate in fixing this with some work around.
I tried few ways by binding the heights, setting some Region constants, etc. But none worked. All the height calculations of FlowPane are indeed correct.
Do any of you have any suggestions on how I can fix this.
Note: The issue can be reproduced in all versions of JavaFX
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
#SuppressWarnings("javadoc")
public class SplitPaneDividerIssueDemo extends Application {
/**
* FlowPane for debugging purpose.
*/
class SimpleFlowPane extends FlowPane {
#Override
protected double computeMaxHeight(final double width) {
final double height = super.computeMaxHeight(width);
// Debugging the first FlowPane in each SplitPane
if (isFirst()) {
System.out.println("Computed max height for " + getId() + " :: " + height);
}
return height;
}
#Override
protected double computeMinHeight(final double width) {
final double height = super.computeMinHeight(width);
if (isFirst()) {
System.out.println("Computed min height for " + getId() + " :: " + height);
}
return height;
}
#Override
protected double computePrefHeight(final double width) {
final double height = super.computePrefHeight(width);
if (isFirst()) {
System.out.println("Computed pref height for " + getId() + " :: " + height);
}
return height;
}
private boolean isFirst() {
return getId().endsWith("-1");
}
}
private int splitId = 1;
private int flowId = 1;
public static void main(final String... a) {
Application.launch(a);
}
#Override
public void start(final Stage primaryStage) throws Exception {
final HBox root = new HBox(buildSplitPane(10), buildSplitPane(20), buildSplitPane(30));
root.setSpacing(10);
final Scene scene = new Scene(root, 1250, 700);
primaryStage.setScene(scene);
primaryStage.setTitle("SplitPane Divider Issue");
primaryStage.show();
}
private VBox buildContent(final int count) {
final Button button = new Button("Button");
final FlowPane flowPane = new SimpleFlowPane();
flowPane.setId("flow-" + splitId + "-" + flowId);
flowPane.setVgap(5);
flowPane.setHgap(5);
for (int i = 0; i < count; i++) {
flowPane.getChildren().add(new Button("" + i));
}
final ScrollPane scroll = new ScrollPane();
VBox.setVgrow(scroll, Priority.ALWAYS);
final ToolBar toolBar = new ToolBar();
toolBar.getItems().add(new Button("Test"));
final VBox pane = new VBox();
pane.setPadding(new Insets(5));
pane.setSpacing(5);
pane.setStyle("-fx-background-color:yellow;-fx-border-width:1px;-fx-border-color:red;");
pane.getChildren().addAll(button, flowPane, scroll, toolBar);
pane.parentProperty().addListener((obs,old,content)->{
if(content!=null){
content.layoutYProperty().addListener((obs1,old1,layoutY)->{
System.out.println("LayoutY of content having "+flowPane.getId()+" :: "+layoutY);
});
}
});
flowId++;
return pane;
}
private SplitPane buildSplitPane(final int count) {
final SplitPane splitPane = new SplitPane();
splitPane.setStyle("-fx-background-color:green;");
splitPane.setOrientation(Orientation.VERTICAL);
splitPane.setDividerPositions(.36, .70);
splitPane.getItems().addAll(buildContent(count), buildContent(count), buildContent(count));
HBox.setHgrow(splitPane, Priority.ALWAYS);
splitId++;
flowId = 1;
return splitPane;
}
}
The problem is within the minHeight of a FlowPane since it is oriented horizontally making that minHeight very dynamic. It appears to be designed where the minHeight is changed as it grows and shrinks in width. When you condense the parent vertically, the VBox calculates its minHeight as the "top/bottom insets plus the sum of each child's min height plus spacing between each child" according to the docs. Apparently there is some problem where a FlowPane's parent cannot account for its minHeight.
An HBox calculates its minHeight as the "top/bottom insets plus the largest of the children's min heights." So, if you wrap the FlowPane in an HBox, that HBox minHeight will be bound to the height of the FlowPane, and then place that HBox in the VBox where the FlowPane should be.
HBox flowPaneContainer = new HBox();
flowPaneContainer.getChildren().add(flowPane);
pane.getChildren().addAll(button, flowPaneContainer, scroll, toolBar);
EDIT: This is fine if your stage size is fixed. If your application is resizable, then more will have to be done because the flowPane minHeight will change, changing the HBox minHeight, and will then result in the same problem because there won't be enough room for everything inside every VBox.
With resizable apps, I normally handle this by wrapping each section of a SplitPane in a ScrollPane.

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.

Textflow inside Tablecell: not correct cell height

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.

JavaFX: How can I best place a Label centered in a Shape?

Let's say I already have a Shape on the screen. For example:
Circle circle = new Circle(x, y, radius);
circle.setFill(Color.YELLOW);
root.getChildren().add(circle);
I would like to create a Label "over" that Circle such that the Label is centered in the Circle, the font size is maximized to fit inside the Circle, etc.
I can see how this could be accomplished via binding, but that seems needlessly complicated if the position/size of these things will never change during runtime.
Thank you in advance for your help! I'm very new to JavaFX and not all that experienced at programming in the first place, so I apologize if I should've been able to find this out via my research.
Use a StackPane to automatically center the text on top of the shape.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.*;
import javafx.stage.Stage;
// java 8 code.
public class Circular extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(final Stage stage) throws Exception {
Text text = createText("Xyzzy");
Circle circle = encircle(text);
StackPane layout = new StackPane();
layout.getChildren().addAll(
circle,
text
);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
private Text createText(String string) {
Text text = new Text(string);
text.setBoundsType(TextBoundsType.VISUAL);
text.setStyle(
"-fx-font-family: \"Times New Roman\";" +
"-fx-font-style: italic;" +
"-fx-font-size: 48px;"
);
return text;
}
private Circle encircle(Text text) {
Circle circle = new Circle();
circle.setFill(Color.ORCHID);
final double PADDING = 10;
circle.setRadius(getWidth(text) / 2 + PADDING);
return circle;
}
private double getWidth(Text text) {
new Scene(new Group(text));
text.applyCss();
return text.getLayoutBounds().getWidth();
}
}
Related
how to put a text into a circle object to display it from circle's center?
The answer to the related question discusses different bounds types for text (such as Visual bounds), in case you need that.
StackPane stackPane = new StackPane();
Circle circle = new Circle();
Label label = new Label("Hi");
circle.setFill(Color.GOLD);
circle.setStroke(Color.GRAY);
circle.radiusProperty().bind(label.widthProperty());
stackPane.getChildren().addAll(circle, label);

Resources