Reputation: 299
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;
public class HorizontalTextAlignment extends javafx.application.Application {
@Override
public void start(Stage stage) {
var rectangle = new Rectangle(600, 100, Color.TURQUOISE);
var text = new Text(rectangle.getWidth() / 2, rectangle.getHeight() / 2, "TEXT");
text.setBoundsType(TextBoundsType.VISUAL);
text.setFont(new Font(100));
text.setTextAlignment(TextAlignment.CENTER);
text.setTextOrigin(VPos.CENTER);
stage.setScene(new Scene(new Pane(rectangle, text)));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I would need to center the text without using any layout. As you may notice, while vertical alignment works, horizontal doesn't. Of course, this could be solved by a simple calculation.
// You need to change the horizontal position of the text for this to work.
var text = new Text(rectangle.getLayoutX(), rectangle.getHeight() / 2, "TEXT");
...
var rectangleWidth = rectangle.getWidth();
var textWidth = text.getLayoutBounds().getWidth();
text.setLayoutX((rectangleWidth - textWidth) / 2);
The problem is, although it's not easy to see in the picture that it's not exactly in the middle.
This is good to see if you only change the initialization.
var text = new Text(rectangle.getLayoutX(), rectangle.getHeight() / 2, "TEXT");
However, this cannot be solved by simply subtracting the desired value (in this case -2) from the horizontal position, because when I change the text.
It is necessary to subtract 9. And that is the problem. I can never know in advance how much must be deducted from the horizontal position.
How do I solve this, please?
Thank you
Upvotes: 0
Views: 1301
Reputation: 2713
If are not forced to use those specific Node
s, I would use Label
instead of Text
and StackPane
instead of Pane
:
Rectangle rectangle = new Rectangle(600, 100, Color.TURQUOISE);
Label label = new Label("TEXT");
label.setAlignment(Pos.CENTER);
label.setFont(new Font(100));
StackPane.setAlignment(label, Pos.CENTER);
StackPane.setAlignment(rectangle, Pos.CENTER);
StackPane pane = new StackPane(rectangle, label);
pane.prefWidthProperty().bind(rectangle.widthProperty());
pane.prefHeightProperty().bind(rectangle.heightProperty());
stage.setScene(new Scene(pane));
stage.show();
Update
Given that you cannot use StackPane, here is a solution completely out of the box.
You can create a new Shape
subtracting the Text
from a Rectangle
. Then you will have two Rectangle
s, one with the text transparent and another one for the background (that will give color to the text).
The benefit of these approach is that you end with two Shapes
with the same bounds and will be easier to layout in the Scene
.
import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
Rectangle rectangle = new Rectangle(600, 100);
Text text = new Text("TEXT");
text.setTextAlignment(TextAlignment.CENTER);
text.setBoundsType(TextBoundsType.VISUAL);
text.setFont(new Font(100));
text.setTextOrigin(VPos.CENTER);
text.setX(rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2);
text.setY(rectangle.getHeight() / 2);
Shape shape = Shape.subtract(rectangle, text);
shape.setFill(Color.TURQUOISE);
shape.layoutXProperty().bind(rectangle.layoutXProperty());
shape.layoutYProperty().bind(rectangle.layoutYProperty());
shape.translateXProperty().bind(rectangle.translateXProperty());
shape.translateYProperty().bind(rectangle.translateYProperty());
stage.setScene(new Scene(new Pane(rectangle, shape)));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Upvotes: 1
Reputation: 44338
Instead of a Text object, you can use a Label with its alignment set to center the text. Then you just need to force the Label’s minimum width to match the parent’s width (which is, roughly, mimicking what some layouts do):
@Override
public void start(Stage stage) {
var rectangle = new Rectangle(600, 100, Color.TURQUOISE);
var text = new Label("TEXT");
text.setFont(new Font(100));
text.setAlignment(Pos.CENTER);
Pane pane = new Pane(rectangle, text);
text.minWidthProperty().bind(pane.widthProperty());
text.minHeightProperty().bind(pane.heightProperty());
stage.setScene(new Scene(pane));
stage.show();
}
Upvotes: 1
Reputation: 45806
This could all be accomplished much more easily by using a StackPane
(e.g. as shown in @Oboe's answer). Using the appropriate layout should always be your first choice. However, the rest of my answer shows how to do it manually.
The x
and y
properties define the origin point of the text node. The x-coordinate determines where the left side of the text starts. However, the y-coordinate can be customized somewhat via the textOrigin
property.
X (always)
|
Y (VPos.TOP)-------╔════════════════════════════════╗
║ ║
Y (VPos.CENTER)----║ Text Node ║
║ ║
Y (VPos.BOTTOM)----╚════════════════════════════════╝
Note: There's also VPos.BASELINE
(the default) but I don't know how to visualize that.
As you can see, when you set the x
property to rectangle.getWidth() / 2
you are aligning the left side of the text node with the horizontal center of the rectangle. If you want to align the horizontal center of the text node with the horizontal center of the rectangle you have to take the width of the text node into account. Here's an example:
import javafx.application.Application;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBoundsType;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
var rectangle = new Rectangle(600, 100, Color.TURQUOISE);
var text = new Text("TEXT");
text.setBoundsType(TextBoundsType.VISUAL);
text.setFont(new Font(100));
text.setTextAlignment(TextAlignment.CENTER);
text.setTextOrigin(VPos.CENTER);
// WARNING: Assumes Rectangle is at (0, 0)
text.setX(rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2);
text.setY(rectangle.getHeight() / 2);
stage.setScene(new Scene(new Pane(rectangle, text)));
stage.show();
}
}
Notice that I set the x
property after setting everything else (e.g. font, text, etc.). That's necessary for the resulting text's width to be known. Of course, this is not responsive; if the the dimensions of the rectangle or text change you have to manually recompute the new x
value. The ideal solution, when we ignore layouts, is to use a bindings. Something like:
// WARNING: Assumes Rectangle is at (0, 0)
text.xProperty()
.bind(
Bindings.createDoubleBinding(
() -> rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2,
rectangle.widthProperty(),
text.layoutBoundsProperty()));
Unfortunately, changing the x
property changes the layout bounds of the text and the above leads to a StackOverflowError
. The same is true of the text's bounds-in-local (this is a characteristic of shapes). There is a solution though if you don't mind positioning the text via its layoutX
and layoutY
properties:
// WARNING: Assumes Rectangle is at (0, 0)
// leave text.x = 0 and text.y = 0
text.layoutXProperty()
.bind(
Bindings.createDoubleBinding(
() -> rectangle.getWidth() / 2 - text.getLayoutBounds().getWidth() / 2,
rectangle.widthProperty(),
text.layoutBoundsProperty()));
// assumes textOrigin = VPos.CENTER
text.layoutYProperty().bind(rectangle.heightProperty().divide(2));
Note the layoutX
and layoutY
properties come from Node
. They're used by layouts to position their children. But since you're not using a layout which automatically positions its children you can safely set them manually. There's also the translateX
and translateY
properties. These add onto the layout properties. For example, ignoring other things, the final x-position will be layoutX + translateX
(similar for the final y-position); for a Text
node the final x-position would be x + layoutX + translateX
(again, similar for the final y-position).
You may have noticed that all my examples warn about assuming the rectangle is at the origin of its parent. To fix that issue you simply have to add the rectangles x and y positions to the text's layout position.
Upvotes: 1