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
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.
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();
}
If are not forced to use those specific Nodes, 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 Rectangles, 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);
}
}
Related
Is it possible with Popup opacity mask top and bottom JavaFX? I have TextField autocomplete with Popup. So the idea is to put an opacity mask.
Below is another way you can give a try, for getting the opacity masked effect. Though it is not exactly the same implementation, I took some ideas from the link you provided :).
I created a small utility where you can pass the Popup instance. The utility builds the mask panes and include to the root node of the Popup.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class PopupOpacityMaskDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
root.setStyle("-fx-background-color:grey;");
root.setOnMouseClicked(e -> {
ListView<String> content = new ListView<>();
content.getItems().addAll(IntStream.range(100, 200).mapToObj(i -> i + "").collect(Collectors.toList()));
content.setPrefSize(200, 250);
Popup popup = new Popup();
popup.setAutoHide(true);
popup.getContent().add(content);
popup.setX(e.getScreenX());
popup.setY(e.getScreenY());
popup.show(root.getScene().getWindow());
MaskUtil.applyMask(popup);
});
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("Demo");
primaryStage.show();
}
static class MaskUtil{
static void applyMask(Popup popup) {
double fadeSize = 70;
Pane pane = (Pane)popup.getScene().getRoot();
// Build the mask panes
Pane topMask = buildMaskPane(pane, fadeSize, false);
Pane bottomMask = buildMaskPane(pane, fadeSize, true);
// Just ensuring to remove any masks (if you are reusing the Popup)
pane.getChildren().removeAll(pane.lookupAll(".mask"));
pane.getChildren().addAll(topMask, bottomMask);
// Update the bottom mask position by listening to height of pane
pane.heightProperty().addListener((obs, old, h) -> bottomMask.setLayoutY(h.doubleValue() - fadeSize));
if (pane.getHeight() > 0) {
bottomMask.setLayoutY(pane.getHeight() - fadeSize);
}
}
private static Pane buildMaskPane(Pane pane, double fadeSize, boolean isBottom) {
Pane mask = new Pane();
mask.setMouseTransparent(true); // Turn this to 'false' if you don't want to interact over mask
mask.setPrefHeight(fadeSize);
mask.prefWidthProperty().bind(pane.widthProperty());
mask.maxHeightProperty().bind(mask.prefHeightProperty());
mask.minHeightProperty().bind(mask.prefHeightProperty());
mask.getStyleClass().add("mask");
mask.setStyle(String.format("-fx-background-color:linear-gradient(to %s, #555555, transparent)", isBottom ? "top" : "bottom"));
return mask;
}
}
}
I am using a BorderPane to implement a layout in JavaFX.
Suppose the BorderPane's maxPrefWidth is set to 1000. There is a button in the left pane, and a button in the right pane. In the center pane, there is another button with an unknown size.
If: the middle element's width is 500, then the left and right nodes
should be 250 in width.
If: the middle element's width is 600, then the left and right nodes
should be 200 in width.
Is there a way to tell the left and right pane to automatically grow (horizontally) until the center node's is hit?
BorderPane expands middle area, by design
Your intentions do not mesh with the design intentions of BorderPane.
To quote the Javadoc:
The top and bottom children will be resized to their preferred heights and extend the width of the border pane. The left and right children will be resized to their preferred widths and extend the length between the top and bottom nodes. And the center node will be resized to fill the available space in the middle. Any of the positions may be null.
This means:
The center expands to take all extra space.
The top and bottom take maximum width and their preferred height.
The left and right areas take their preferred width and maximum height.
Imagine the middle as a box with a strongman inside pushing up, down, and out.
This logic is often quite handy for many business apps. The outside areas are often used for navigation, breadcrumbs, menu bar, tool bar, status bar, and so on. The inner area then holds the main content of interest. Given such usage, it makes sense to allocate only necessary space to the outer areas, and allocate most space to the inner content area.
For example, here is an entire example app using JavaFX 14.
In the center area of this example app, we place an HBox layout containing a single button. We set the background color of that layout to cornflowerblue color to make visible how the content of the center area expands to take all extra space.
package work.basil.example;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.time.ZonedDateTime;
/**
* JavaFX App
*/
public class App extends Application
{
#Override
public void start ( Stage stage )
{
// Widgets
ToolBar toolBar = new ToolBar();
Button button = new Button( "Click Me" );
toolBar.getItems().add( button );
Button buttonLeft = new Button( "Left" );
Button buttonRight = new Button( "Right" );
HBox appContent = new HBox( new Button( "Bonjour le monde" ) );
appContent.setStyle( "-fx-background-color: cornflowerblue;" );
HBox statusBar = new HBox( new Label( "Status goes here. Now: " + ZonedDateTime.now() ) );
// Arrange
BorderPane borderPane = new BorderPane();
borderPane.setTop( toolBar );
borderPane.setLeft( buttonLeft );
borderPane.setCenter( appContent );
borderPane.setRight( buttonRight );
borderPane.setBottom( statusBar );
var scene = new Scene( borderPane , 1000 , 1000 );
stage.setScene( scene );
stage.show();
}
public static void main ( String[] args )
{
launch();
}
}
Choose another layout
As commented on the Question, you should be using a different layout manager given your intentions.
You might be able to get by with a HBox. For maximum control, you will need to invest some time into mastering the GridPane.
GridPane
Your Question is not completely clear. If what you want is for the center content to be fixed width of 500 pixels while the left and right are flexible, being allocated any extra space proportionally, then use GridPane while setting the ColumnConstraints to Priority.SOMETIMES or Priority.ALWAYS on the left and right cells.
Here is a complete example app.
We put one button, each nested in a colored HBox, in each cell of the single-row GridPane. The colors dramatize the sizing behavior being shown here. Alternatively, you could drop the colored HBox, instead calling gridPane.setStyle( "-fx-grid-lines-visible: true ;" ) to show border lines around each cell.
package work.basil.example;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.time.ZonedDateTime;
/**
* JavaFX App
*/
public class App extends Application
{
#Override
public void start ( Stage stage )
{
// Widgets
Button buttonLeft = new Button( "Left" );
HBox left = new HBox( buttonLeft );
left.setStyle( "-fx-background-color: Salmon;" );
Button buttonCenter = new Button( "Center" );
HBox center = new HBox( buttonCenter );
center.setStyle( "-fx-background-color: CornflowerBlue;" );
Button buttonRight = new Button( "Right" );
HBox right = new HBox( buttonRight );
right.setStyle( "-fx-background-color: MediumSeaGreen;" );
// GridPane
GridPane gridPane = new GridPane();
gridPane.addRow( 0 , left , center , right ); // Add these widgets in first row (annoying zero-based counting means index 0 is row 1).
gridPane.setStyle( "-fx-grid-lines-visible: true ;" ); // Add lines to the edges of each cell (row/column) in the grid. Useful for learning and debugging. https://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html#gridpane
// Grid constraints
ColumnConstraints column1 = new ColumnConstraints();
column1.setHgrow( Priority.SOMETIMES ); // Extra space alloted to this column.
ColumnConstraints column2 = new ColumnConstraints( 500 ); // Fixed width of 500 pixels.
ColumnConstraints column3 = new ColumnConstraints();
column3.setHgrow( Priority.SOMETIMES );// Extra space alloted to this column.
gridPane.getColumnConstraints().addAll( column1 , column2 , column3 ); // first column gets any extra width
// Render
var scene = new Scene( gridPane , 1000 , 150 );
stage.setScene( scene );
stage.setTitle( "Example of JavaFX GridPane, by Basil Bourque" );
stage.show();
}
public static void main ( String[] args )
{
launch();
}
}
Screenshot of the app running. Notice how the left and right get remaining space of 250 pixels each. We set the window (Stage) to 1,000 pixels, and fixed the width of the center piece to 500 pixels. That leaves 500 pixels remaining to allocate. Both left and right cells were set to the same priority level, so they split the space evenly between them: 500/2 = 250 pixels each.
If the user narrows the width of the window to 600 pixels, the left and right cells will be 50 pixels each: 600 - 500 = 100, 100/2 = 50 pixels each.
Generally I would recommend posting code rather than attempting to describe it. For example this mre can represent your question:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
Button leftBtn = new Button("Left");
Button centerBtn = new Button("Center");
centerBtn.widthProperty().addListener((obs, oldValue,newValue)-> {
//change the logic as needed
leftBtn.setPrefWidth(newValue.doubleValue() >= 600 ? 200 : 250);
});
centerBtn.setPrefSize(600, 0);
Button rightBtn = new Button("Right");
Pane root = new BorderPane(centerBtn, null, rightBtn, null, leftBtn);
root.setPrefSize(1000, 150);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This also makes helping much easier. For example a solution with HBox as proposed in the comments just requires minor changes in the mre (action box added to the center button to change its width):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Main extends Application {
private static final double MIN = 300, MAX = 700, DELTA = 100;
private Button centerBtn;
#Override
public void start(Stage stage) throws Exception {
Button leftBtn = new Button("Left");
HBox.setHgrow(leftBtn, Priority.NEVER);
centerBtn = new Button("Click to change width");
HBox.setHgrow(leftBtn, Priority.NEVER);
centerBtn.widthProperty().addListener((obs, oldValue,newValue)-> {
//change the logic as needed
leftBtn.setPrefWidth(newValue.doubleValue() >= 600 ? 200 : 250);
});
centerBtn.setPrefSize(600, 0);
centerBtn.setOnAction(e-> changeCenterBtnWidth());
Button rightBtn = new Button("Right");
HBox.setHgrow(rightBtn, Priority.ALWAYS);
Pane root = new HBox(leftBtn,centerBtn,rightBtn);
root.setPrefSize(1000, 150);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private void changeCenterBtnWidth() {
double newWidth = centerBtn.getWidth() + DELTA;
newWidth = newWidth < MAX ? newWidth : MIN;
centerBtn.setPrefWidth(newWidth);
}
public static void main(String[] args) {
launch(args);
}
}
Demonstrating a solution based on GridPane rquires only some samll changes:
public void start(Stage stage) throws Exception {
Button leftBtn = new Button("Left");
centerBtn = new Button("Click to change width");
centerBtn.widthProperty().addListener((obs, oldValue,newValue)-> {
//change the logic as needed
leftBtn.setPrefWidth(newValue.doubleValue() >= 600 ? 200 : 250);
});
centerBtn.setPrefSize(600, 0);
centerBtn.setOnAction(e-> changeCenterBtnWidth());
Button rightBtn = new Button("Right");
GridPane root = new GridPane();
root.add(leftBtn,0, 0);
root.add(centerBtn,1, 0);
root.add(rightBtn,2, 0);
root.setPrefSize(1000, 150);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
The code below generates this screen shot:
The label below the 2nd column does not want to properly claim extra space when it needs to be wrapped. This only occurs when the columns use percentage widths -- the docs say it will ignore all other properties in that case, including hgrow etc, but it does not mention it would also affect how rows work.. but it looks like it does.
Anyone got a work-around or can tell me what I'm doing wrong? All I want is to display 3 images, with labels of unknown size below them that are nicely spaced and aligned and all the same size... Something like this:
The above is done with a custom control, tentatively named "BetterGridPane"...
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.stage.Stage;
public class TestLabelWrap extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
GridPane root = new GridPane();
root.add(new ListView(FXCollections.observableArrayList("1", "2", "3")), 0, 0);
root.add(new ListView(FXCollections.observableArrayList("1", "2", "3")), 1, 0);
root.add(new ListView(FXCollections.observableArrayList("1", "2", "3")), 2, 0);
root.add(new Label("Value A"), 0, 1);
root.add(new Label("The value for this porperty is so large it wraps, The value for this porperty is so large it wraps") {{
setWrapText(true);
}}, 1, 1);
root.add(new Label("Value C"), 2, 1);
root.getColumnConstraints().add(new ColumnConstraints() {{ setPercentWidth(33.33); }});
root.getColumnConstraints().add(new ColumnConstraints() {{ setPercentWidth(33.33); }});
root.getColumnConstraints().add(new ColumnConstraints() {{ setPercentWidth(33.33); }});
root.getRowConstraints().add(new RowConstraints() {{ setVgrow(Priority.NEVER); }});
root.getRowConstraints().add(new RowConstraints() {{ setVgrow(Priority.ALWAYS); }});
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Setting wrapText to true actually seems to be enough. You can see this by simply increasing the height of your window and watch the text start to wrap (decreasing the width of the window as necessary). The problem seems to be GridPane and it's initial size calculations—it grows horizontally to accommodate the Label, but not vertically. One fix is to simply give the Scene, GridPane, or Label an explicit (preferred) width while leaving the height to be computed.
new Scene(root, someWidth, -1)
root.setPrefWidth(someWidth)
label.setPrefWidth(someWidth) + label.setMaxWidth(Double.MAX_VALUE)
However, even though the Javadoc of ColumnConstraints.percentWidth says:
If set to a value greater than 0, the column will be resized to this percentage of the gridpane's available width and the other size constraints (minWidth, prefWidth, maxWidth, hgrow) will be ignored.
The [min|pref|max]Width does not seemed to be ignored during preferred size calculations. As you appear to have images of known and uniform width, you should be able to set the preferred width of each constraint to be slightly larger than the width of the image. Here's an example (using OpenJDK 12 and OpenJFX 12):
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
public class Main extends Application {
private static ColumnConstraints[] createColumnConstraints() {
return Stream.generate(() -> {
var constraint = new ColumnConstraints();
constraint.setHalignment(HPos.CENTER);
constraint.setPercentWidth(100.0 / 3.0);
constraint.setPrefWidth(225.0); // SET PREF WIDTH
return constraint;
}).limit(3).toArray(ColumnConstraints[]::new);
}
private static Rectangle[] createRectangles() {
return Stream.generate(() -> new Rectangle(100.0, 150.0)).limit(3).toArray(Rectangle[]::new);
}
private static Label[] createLabels(String... texts) {
return Stream.of(texts).map(text -> {
var label = new Label(text);
label.setWrapText(true);
label.setTextAlignment(TextAlignment.CENTER);
return label;
}).toArray(Label[]::new);
}
#Override
public void start(Stage primaryStage) {
var root = new GridPane();
root.setGridLinesVisible(true); // FOR VISUAL CLARITY
root.setHgap(10.0);
root.setVgap(10.0);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20.0));
root.getColumnConstraints().addAll(createColumnConstraints());
root.addRow(0, createRectangles());
root.addRow(1, createLabels("Label A", "This is long text. ".repeat(10), "Label C"));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Which results in:
Speculation:
Since you use new Scene(root) that means the Scene will size itself to the preferred size of its content. This basically gives the root free reign to grow as large as it deems necessary. As documented by GridPane:
By default, rows and columns will be sized to fit their content; a column will be wide enough to accommodate the widest child, a row tall enough to fit the tallest child.
With the use of percentWidth, the width of each column effectively doesn't have an upper bound; they will grow to to fit the widest child. Also, because each column uses percentWidth, as one grows, they all grow.
This means the Labels have virtually unlimited space to grow horizontally, which means the preferred height of the Label is calculated to be only one line of text; why wrap when you have infinite width? Because of this, the row the Labels belong to only grows vertically enough to accommodate one line of text. With no room to wrap the text overruns.
I am starting to learn JavaFX
now when I try to rotate the image view using
imgv.setRotate(angle);
it rotates around its central axis
but when I try to rotate it around a custom point inside the image
using
imgv.getTransforms().add(new Rotate(Angle,custom_x,custom_y);
it rotates randomly and I can't figure its axis of the rotation
this was the same as
imgv.getTransforms().add(new Rotate(Angle,custom_x,custom_y,1.0,Rotate.Z_AXIS);
is there any way to rotate the image around a custom point
here is image explaining the position of a point if (0,0) was at the top left or at the center of the image both way I can't rotate the image around that point according to x,y
I know that I can rotate around the origin then do translation but am asking if there is a speedway to do that
thanks in advance
Here is an app that demonstrates how to accomplish what you are asking. The key parts of accomplishing this are .getTransforms().add(..) and Rotate. Comments in the code. I added the Circle so that you can actually see where the point of rotation is located.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication117 extends Application
{
#Override
public void start(Stage primaryStage)
{
Image image = new Image("http://lmsotfy.com/so.png");
ImageView imageView = new ImageView(image);
imageView.setFitHeight(400);
imageView.setFitWidth(400);
Button btn = new Button();
btn.setText("Say 'Hello World'");
//Use this Circle to help see where the rotation occurs
Circle circle = new Circle(5);
circle.setFill(Color.RED);
circle.setCenterX(100);
circle.setCenterY(300);
//Add the Rotate to the ImageView's Transforms
Rotate rotation = new Rotate();
rotation.setPivotX(circle.getCenterX());//Set the Pivot's X to be the same location as the Circle's X. This is only used to help you see the Pivot's point
rotation.setPivotY(circle.getCenterY());//Set the Pivot's Y to be the same location as the Circle's Y. This is only used to help you see the Pivot's point
imageView.getTransforms().add(rotation);//Add the Rotate to the ImageView
//Use the Button's handler to rotate the ImageView
btn.setOnAction((ActionEvent event) -> {
rotation.setAngle(rotation.getAngle() + 15);
});
Pane pane = new Pane();
pane.getChildren().addAll(imageView, circle);
VBox.setVgrow(pane, Priority.ALWAYS);
VBox vBox = new VBox(pane, new StackPane(btn));
StackPane root = new StackPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 1080, 720);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
I have started programming this app for ploting temperature data, when the screen gets resized, the canvas should resize as well. It initializes correctly but when I want to resize the window, the draw() method will only resize the height, according to the stackpanes height value it is bound to but it will ignore the width entirely. The Listener won't even fire. This I find very strange. Also I have to set the minSize for the StackPane, otherwise nothing will be draw at all. I'm not using FXML.
Does anybody have any ideas?
Edit I changed line 58 from setRight() to setCenter, as mentioned in the solution from InternetUnexplorer. But I was still curious about why it is so, so I did some research. I found this in the internet:
Top/Bottom area: Can shrink/expand horizontally and keep the height
unchanged.
Left/Right area: Can shrink/expand vertically and keep the length
unchanged.
Center area: Can shrink/expand in both directions.
http://o7planning.org/en/10629/javafx-borderpane-layout-tutorial
Here my main class:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class main extends Application {
#Override
public void start(Stage mainStage) throws Exception {
TempViewerWindow view = new TempViewerWindow();
ReadData controller = new ReadData(view);
controller.selectAll();
mainStage.setScene(new Scene(view));
//mainStage.setMinWidth(500);
//mainStage.setMinHeight(400);
mainStage.setTitle("Temperature Viewer");
mainStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
And here my view class:
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
public class TempViewerWindow extends BorderPane {
public TableView<TempData> tableView;
public Canvas canvas;
public GraphicsContext gc;
public StackPane holder;
public TempViewerWindow() {
tableView = new TableView<>();
TableColumn<TempData, String> col_date = new TableColumn<>("Date");
TableColumn<TempData, Float> col_temperature = new TableColumn<>("Temperature");
col_date.setCellValueFactory(e -> e.getValue()
.dateProperty());
col_temperature.setCellValueFactory(e -> e.getValue()
.temperatureProperty()
.asObject());
tableView.getColumns()
.addAll(col_date, col_temperature);
setLeft(tableView);
holder = new StackPane();
holder.setMinSize(400, 400); // must be here for some reason
canvas = new Canvas();
gc = canvas.getGraphicsContext2D();
canvas.widthProperty()
.bind(holder.widthProperty()
.subtract(10));
canvas.heightProperty()
.bind(holder.heightProperty()
.subtract(10));
canvas.widthProperty()
.addListener(observable -> redraw(gc));
canvas.heightProperty()
.addListener(observable -> redraw(gc));
holder.getChildren()
.add(canvas);
setCenter(holder); //here was the bug
}
private void redraw(GraphicsContext gc) {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
System.out.println("redraw!");
}
}
Your canvas is being placed in the right of your BorderPane, when you should be placing it in the center.
If you change line 54 of TempViewerWindow from setRight(holder) to setCenter(holder), the scaling works properly.
You're using a BorderPane as parent for holder. The holder is used as right child of the BorderPane. However the right/left children are resized to their preferred widths and are resized to fill the height between the top and bottom nodes. (See javadoc)
The left and right children will be resized to their preferred widths and extend the length between the top and bottom nodes.
If you'd use holder as center, it's width would be adjusted the way you expect it.
Alternatively you could use a HBox as base class for TempViewerWindow to achieve this kind of behavior.
public TempViewerWindow() {
...
this.getChildren().addAll(tableView, holder);
HBox.setHgrow(holder, Priority.ALWAYS);
HBox.setHgrow(tableView, Priority.NEVER);
this.setFillHeight(true);
}