How to center/wrap/truncate Text to fit within Rectangle in JavaFX 2.1?


Question

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?

1
2
5/17/2012 9:50:10 AM

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:

enter image description here

A couple of notes:

  1. It is usually best to use a Label rather than trying to recreate part of a Label's functionality.

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

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

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

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

5
5/18/2012 10:56:08 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon