How to center/wrap/truncate Text to fit within Rectangle in JavaFX 2.1?
javafx, javafx-2
Solution
Here is a sample alternate implementation.
It uses a subclass of `Group` with a `layoutChildren` implementation rather than the binding api.
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.geometry.VPos;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.*;
import javafx.stage.Stage;
public class TextInRectangle extends Application {
public static void main(String[] args) throws Exception { launch(args); }
public void start(final Stage stage) throws Exception {
TextBox text = new TextBox("All roads lead to Rome", 100, 100);
text.setLayoutX(30);
text.setLayoutY(20);
final Scene scene = new Scene(text, 160, 140, Color.CORNSILK);
stage.setScene(scene);
stage.show();
}
class TextBox extends Group {
private Text text;
private Rectangle rectangle;
private Rectangle clip;
public StringProperty textProperty() { return text.textProperty(); }
TextBox(String string, double width, double height) {
this.text = new Text(string);
text.setTextAlignment(TextAlignment.CENTER);
text.setFill(Color.FORESTGREEN);
text.setTextOrigin(VPos.CENTER);
text.setFont(Font.font("Comic Sans MS", 25));
text.setFontSmoothingType(FontSmoothingType.LCD);
this.rectangle = new Rectangle(width, height);
rectangle.setFill(Color.BLACK);
this.clip = new Rectangle(width, height);
text.setClip(clip);
this.getChildren().addAll(rectangle, text);
}
@Override protected void layoutChildren() {
final double w = rectangle.getWidth();
final double h = rectangle.getHeight();
clip.setWidth(w);
clip.setHeight(h);
clip.setLayoutX(0);
clip.setLayoutY(-h/2);
text.setWrappingWidth(w * 0.9);
text.setLayoutX(w / 2 - text.getLayoutBounds().getWidth() / 2);
text.setLayoutY(h / 2);
}
}
}
Sample output of the sample app:
A couple of notes:
It is usually best to use a `Label` rather than trying to recreate part of a Label's functionality.
The layout in `layoutChildren` approach is a similar to that used by the JavaFX team in implementing the JavaFX control library. There are probably reasons they use `layoutChildren` rather than binding for layout, but I am not aware of what all of those reasons are.
I find for simple layouts, using the pre-built controls and layout managers from the JavaFX library is best (for instance the above control could have been implemented using just a Label or Text in StackPane). Where I can't quite get the layout I need from the built-in layouts, then I will supplement their usage with bindings, which I find also very easy to work with. I don't run into the need to use `layoutChildren` that much. It's probably just a question of scaling up to lay out complex node groups - most likely doing the calculations in the `layoutChildren` method performs better and may be easier to work with and debug when applied to complex node groups.
Rather than truncating `Text` by calculating a text size and eliding extra characters from a `String` as a `Label` does, the code instead calls `setClip` on the text node to visually clip it to the rectangle's size. If instead you would like to truncate `Text` more like a `Label`, then you could look at the code for the JavaFX utility class which does the computation of clipped text.
The sample code in the question does not compile because it is missing a bracket on the `wrappingWidthProperty` expression and it uses `getValue` and `getWidth` methods inside a bind expression, which is not possible - instead it needs to use a listener on the `boundsInLocalProperty`.
Also, created a small sample app demoing adding text placed in a Label with a rectangular background to a Pane with precise control over the x,y position of the labeled rectangle via binding.
Problem
I need to dynamically create Rectangles over Pane in JavaFX 2.1. Next I need to center/wrap/truncate Text over the Rectangle. The text has to fit within the rectangle. I am able to center and wrap the text with the following code, however, if the text length is too long, it will appear out of the rectangle. I want to create the behavior like Label within StackPane, essentially if the Rectangle grows, the Text will grow with it but always remain in the center of the Rectangle and if the Text cannot fit within the Rectangle, it will be truncated accordingly. ``` Rectangle r; Text t; ... //center and wrap text within rectangle t.wrappingWidthProperty().bind(rect.widthProperty().multiply(0.9); t.xProperty().bind(rect.xProperty().add(rect.widthProperty().subtract(t.boundsInLocalProperty().getValue().getWidth().divide(2))); t.yProperty().bind(rect.yProperty().add(rect.heightProperty().divide(2))); t.setTextAlignment(TextAlignment.CENTER); t.setTextOrigin(VPos.CENTER); ``` What properties can I use to achieve that or is there a better way of doing this?