JavaFX 2: resizable rectangle containing text


Question

I want to display a rectangle that contains a text/label. For this purpose I created a stackpane and added a rectangle and a label to it. However the text/label is not centered correctly. It is placed outside of the rectangle (to the left). This is the code I am currently using:

createRectangle(String name) {
   pane = new StackPane();
   text = new Label(name);
   rect = new Rectangle();
   // bind rectangle width to text width and add 10 
   rect.widthProperty().bind(text.widthProperty().add(10));
   rect.heightProperty().bind(text.heightProperty().add(10));

   // add to stackpane
   pane.getChildren().addAll(rect,text);

   // display stackpane
   getChildren().add(pane)
}

I have tried binding the xProperty() and yProperty() of the rectangle, changing the alignment of the stackpane (setAlignment(Pos.CENTER)) and other things, but without success.

When I use a fixed rectangle size (e.g. new Rectangle(30,30)) and do not use the bindings, the label is centered correctly within the rectangle. However, the rectangle's size needs to adjust depending on the label size:

// label is placed correctly in the center of the rectangle
createRectangle(String name) {
   pane = new StackPane();
   text = new Label(name);
   rect = new Rectangle(30,30);

   // add to stackpane
   pane.getChildren().addAll(rect,text);

   // display stackpane
   getChildren().add(pane)
}
1
1
7/30/2014 4:50:11 PM

Accepted Answer

For a Background Sized to the Label

Just use CSS directly on the Label, you don't need any other nodes.

Label label = new Label("Sally collects seashells on the seashore");
label.setStyle("-fx-background-color: coral; -fx-padding: 10px;");

The padding can be used to adjust the size of the rectangular background area around the label.

fixed label

For a Resizable Background

You don't need a Rectangle node, put the label in a StackPane and set a background on the StackPane:

Label label = new Label("Sally collects seashells on the seashore");
StackPane centeredLabel = new StackPane(label);
centeredLabel.setStyle("-fx-background-color: coral;");

centered label

Executable sample

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class LabelBackground extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Label label = new Label("Sally collects seashells on the seashore");
        StackPane centeredLabel = new StackPane(label);
        centeredLabel.setStyle("-fx-background-color: coral;");

        StackPane root = new StackPane(centeredLabel);
        root.setPadding(new Insets(10));

        stage.setScene(new Scene(root));
        stage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
} 

Note: Inline styles in this answer are just for easy demonstration purposes, best practice is to use a CSS stylesheet for CSS style definitions.

I also want to use other shapes (especially rounded rectangles and circles) instead of plain rectangles. Is this achievable by styling the stackpane or the label itself as proposed in your answer?

Yes, anything which derives from Region (which includes layout panes such as StackPane and Controls such as Label), can be styled pretty extensively via CSS.

To get a rounded background, you can use -fx-background-radius.

To get an arbitrary shaped background, you can use fx-shape, though you may find it tricky to position your text within the arbitrarily shaped background.

You can also write a shape in code and use bindings to make things fit inside it as you originally tried in your question.

If things need a lot of fine-tuning then you can subclass region and over-ride the layoutChildren method to perform precise layout calculations for all of the children inside that method (this is the way that most of the in-built JavaFX controls handle layouts in their skin classes).

Anyway, a seemingly simple question doesn't always have a simple answer, because there ends up lots of variations both in the way you can do things and many subtle differences in what you might want to do, such as how to handle wrapped text, insets, whether to elide the text beyond a minimum size, etc.

However, for the most common tasks of rectangular or rounded rectangular backgrounds, I'd just stick to using CSS styles directly on labels. By using style sheets, you can easily completely change the app style for your labels.

Here is a more extensive, overkill sample:

lotsastyles

LabelBackground.java

import javafx.application.Application;
import javafx.beans.binding.DoubleBinding;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.shape.Ellipse;
import javafx.stage.Stage;

public class LabelBackground extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Pane starLabel = labelOnStyledBackground(
                "Practical Principles of Plain and Perfect Pronunciation.",
                "star-label"
        );

        StackPane labelOnResizableBackground = labelOnStyledBackground(
                "Sally collects seashells on the seashore",
                "resizable-background"
        );
        labelOnResizableBackground.setPrefHeight(60);

        Label squareLabel = createLabel(
                "Betty Botter bought a bit of butter.",
                "square-label"
        );

        Label roundedLabel = createLabel(
                "Peter Piper picked a peck of pickled peppers.",
                "rounded-label"
        );

        Node ellipticalLabel = createEllipticalLabel(
                "Round and round the rugged rock the ragged rascal ran.",
                "elliptical-label"
        );

        VBox root = new VBox(
                10,
                starLabel,
                labelOnResizableBackground,
                squareLabel,
                roundedLabel,
                ellipticalLabel
        );
        VBox.setVgrow(labelOnResizableBackground, Priority.SOMETIMES);
        root.setAlignment(Pos.TOP_CENTER);
        root.setPadding(new Insets(10));

        Scene scene = new Scene(root);
        scene.getStylesheets().add(
                LabelBackground.class.getResource(
                        "label-styles.css"
                ).toExternalForm()
        );
        stage.setScene(scene);
        stage.setTitle("Resize me!");
        stage.show();
    }

    private Label createLabel(String text, String styleClass) {
        Label label = new Label(text);
        label.getStyleClass().add(styleClass);

        return label;
    }

    private StackPane labelOnStyledBackground(String text, String styleClass) {
        Label label = new Label(text);
        StackPane container = new StackPane(label);
        container.getStyleClass().add(styleClass);

        return container;
    }

    private Group createEllipticalLabel(String text, String styleClass) {
        final double INSET = 20;

        Label label = new Label(text);

        Ellipse ellipse = new Ellipse();
        ellipse.getStyleClass().add("ellipse");

        DoubleBinding halfWidth  = label.widthProperty().divide(2).add(INSET);
        DoubleBinding halfHeight = label.widthProperty().divide(4).add(INSET);

        ellipse.radiusXProperty().bind(halfWidth);
        ellipse.radiusYProperty().bind(halfHeight);

        ellipse.centerXProperty().bind(halfWidth);
        ellipse.centerYProperty().bind(halfHeight);

        label.setLayoutX(INSET);
        label.layoutYProperty().bind(halfHeight.subtract(label.heightProperty().divide(2)));

        Group group = new Group(ellipse, label);
        group.getStyleClass().add(styleClass);

        return group;
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
} 

label-styles.css

.root {
    -fx-font-size: 16px;
}

.rounded-label {
    -fx-background-color: cadetblue;
    -fx-padding: 10px;
    -fx-background-radius: 10px;
}

.square-label {
    -fx-background-color: plum;
    -fx-padding: 10px;
}

.star-label {
    -fx-background-color: gold;
    -fx-padding: 120px;
    /* shape courtesy of Smiffy's star place: http://www.smiffysplace.com/stars.html */
    -fx-shape: "M 0.000 20.000 L 23.511 32.361 L 19.021 6.180 L 38.042 -12.361 L 11.756 -16.180 L 0.000 -40.000 L -11.756 -16.180 L -38.042 -12.361 L -19.021 6.180 L -23.511 32.361 L 0.000 20.000";
}

.resizable-background {
    -fx-background-color: coral;
}

.elliptical-label .ellipse {
    -fx-fill: lightpink;
}

If you are super-keen for further samples, look at the way the QuoteMaker app handles styling of resizable label backgrounds.

8
7/31/2014 7:02:29 AM

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