I'm using JavaFx 8.
From the JavaDoc of javafx.scene.Node.scaleYProperty():
[...] This scale factor is not included in layoutBounds by default, which makes it ideal for scaling the entire node after all effects and transforms have been taken into account. [...]
How can I include the scaling factor in layoutBounds, though?
Some context:
In the following example, when pressing the button I would like the GridPane to react also to the scaling of the HBox whithout having to hardcode the prefHeight of the RowConstraints.
Being able to include the scaling factor into the layoutBounds probably would do the trick, but other solutions are welcome as well.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class ScalingStandAlone extends Application {
private VBox vBox = new VBox();
private GridPane gridPane = new GridPane();
private HBox hBox = new HBox();
private ToggleButton button = new ToggleButton("Click to scale");
private Label firstRowLabel = new Label("Some content in text form");
private Label secondRowLabel = new Label("Some content for scaling");
private Label thirdRowLabel = new Label("Some moving content");
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
AnchorPane root = new AnchorPane();
AnchorPane.setTopAnchor(vBox, 5.);
AnchorPane.setLeftAnchor(vBox, 5.);
AnchorPane.setRightAnchor(vBox, 5.);
root.autosize();
Scene scene = new Scene(root);
stage.setTitle("GridRow Scale Demo");
stage.setWidth(400);
stage.setHeight(300);
stage.setScene(scene);
stage.show();
root.getChildren().add(vBox);
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().add(gridPane);
vBox.getChildren().add(button);
vBox.setStyle("-fx-spacing: 15;");
configureGridPane(root);
button.setOnAction(event -> {
hBox.setScaleY(button.isSelected() ? 2 : 1);
});
}
private void configureGridPane(Pane root) {
hBox.getChildren().add(secondRowLabel);
// Styling //
firstRowLabel.setStyle("-fx-padding: 5;");
hBox.setStyle("-fx-background-color: #800000; -fx-padding: 5;");
secondRowLabel.setStyle("-fx-text-fill: white; -fx-padding: 5;");
thirdRowLabel.setStyle("-fx-padding: 5;");
gridPane.add(firstRowLabel, 0, 0);
gridPane.add(hBox, 0, 1);
gridPane.add(thirdRowLabel, 0, 2);
gridPane.setGridLinesVisible(true);
gridPane.getColumnConstraints().add(new ColumnConstraints());
gridPane.getColumnConstraints().get(0).setPercentWidth(100);
}
}
From the Javadocs for Group:
Any transform, effect, or state applied to a Group will be applied to
all children of that group. Such transforms and effects will NOT be
included in this Group's layout bounds, however if transforms and
effects are set directly on children of this Group, those will be
included in this Group's layout bounds.
(my emphasis added).
Therefore, if you simply wrap your HBox in a Group, you will achieve the desired effect:
// gridPane.add(hBox, 0, 1);
gridPane.add(new Group(hBox), 0, 1);
Related
I have a StackPane where I need to add four text fields and a chart. These text fields will be used to set the range of the chart axes. I chose StackPane because it rescales nicely (together with the chart) in the dock pane in which it is later included. I didn't embed the text fields in a VBox or HBox because they gave me troubles with a zooming rectangle (scalable) added later to the pane.
Everything works apart from the fact that I am not able to set the distance between the TextFields and StackPane near borders. All I could control is the general positioning like pane.setAlignment(upperY, Pos.TOP_LEFT). But this means that two of my TextFields overlap in the bottom-left corner. Anyone knows how to fix this, either through CSS or directly in the Java code?
I tried setting -fx-alignment in the CSS. I tried changing the type of pane. I tried setting border insets in the CSS etc.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application
{
static LineChart<Number, Number> chart;
#Override
public void start(Stage stage) throws Exception
{
TextField upperY = new TextField("upperY"); upperY.getStylesheets().add("css/chart.css");
TextField lowerY = new TextField("lowerY"); lowerY.getStylesheets().add("css/chart.css");
TextField rightX = new TextField("rightX"); rightX.getStylesheets().add("css/chart.css");
TextField leftX = new TextField("leftX"); leftX.getStylesheets().add("css/chart.css");
StackPane pane = new StackPane();
HBox.setHgrow(pane, Priority.ALWAYS);
VBox.setVgrow(pane, Priority.ALWAYS);
pane.setAlignment(upperY, Pos.TOP_LEFT);
pane.setAlignment(lowerY, Pos.BOTTOM_LEFT);
pane.setAlignment(rightX, Pos.BOTTOM_RIGHT);
pane.setAlignment(leftX, Pos.BOTTOM_LEFT);
chart = new LineChart<>(new NumberAxis(), new NumberAxis());
pane.getChildren().addAll(chart, upperY, lowerY, leftX, rightX);
Pane root = new Pane();
root.getChildren().add(pane);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} // end start
public static void main(String[] args)
{
launch(args);
}
}
.text-field{
-fx-font-size: 10pt;
-fx-min-width: 35;
-fx-pref-width: 35;
-fx-max-width: 35;
-fx-min-height: 18;
-fx-pref-height: 18;
-fx-max-height: 18;
-fx-font-family: "Arial Narrow";
-fx-text-fill: black;
-fx-alignment: BASELINE_CENTER;
-fx-padding: -2 2 0 2; /* // T R B L*/
-fx-background-color: white;
-fx-control-inner-background: white;
-fx-border-color: lightgray;
-fx-border-insets: 0 0 0 0;
-fx-background-insets: 0 0 0 0;
}
I need to push the leftX text field to the right and the lowerY text field upwards a little bit.
The layout works similar to a GridPane. The only problem is that using column/row spans for the chart results in the chart not being used for the calculations of the preferred size of the GridPane.
However you could use a HBox and a VBox to hold the bottom and left textfields respectively and place the VBox and the chart in the first row and the HBox in the cell below the chart.
The following example uses a Region instead of a chart for simplicity, but adjusting the code to use a different node type is trivial:
private static TextField createTextField(String prompt) {
TextField tf = new TextField(prompt);
return tf;
}
#Override
public void start(Stage primaryStage) throws Exception {
// Using simple region instead of a chart for simplicity here
Region chart = new Region();
chart.setStyle("-fx-background-color: red;");
chart.setPrefSize(300, 300);
// create textfield container to the left
Region placeholder = new Region();
VBox.setVgrow(placeholder, Priority.ALWAYS);
VBox left = new VBox(
createTextField("upperY"),
placeholder,
createTextField("lowerY"));
// create textfield container to the bottom
placeholder = new Region();
HBox.setHgrow(placeholder, Priority.ALWAYS);
HBox bottom = new HBox(
createTextField("leftX"),
placeholder,
createTextField("rightX"));
GridPane container = new GridPane();
container.add(left, 0, 0);
container.add(bottom, 1, 1);
container.add(chart, 1, 0);
// make sure the row/column containing the chart is the one that grows
ColumnConstraints cConstraints = new ColumnConstraints();
cConstraints.setHgrow(Priority.ALWAYS);
container.getColumnConstraints().addAll(new ColumnConstraints(), cConstraints);
RowConstraints rConstraints = new RowConstraints();
rConstraints.setVgrow(Priority.ALWAYS);
container.getRowConstraints().addAll(rConstraints, new RowConstraints());
Scene scene = new Scene(container);
primaryStage.setScene(scene);
primaryStage.show();
}
I would like to create a BorderPane layout in JavaFX with no center pane.
The code I have written so far only implements the left and right borders and is below:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class GUI_Practice extends Application {
#Override
public void start(Stage stage) throws Exception {
String blackBorder = "-fx-border-style: solid; -fx-border-width: 1; -fx-border-color: black";
/* Left column */
Button save = new Button("Save");
Button del = new Button("Delete");
HBox settings = new HBox(save, del);
VBox leftCol = new VBox(settings);
leftCol.setStyle(blackBorder);
/* Right column */
Button calculate = new Button("Calculate");
Button cancel = new Button("Cancel");
HBox runButtons = new HBox(calculate, cancel);
VBox rightCol = new VBox(runButtons);
rightCol.setStyle(blackBorder);
/* Set up borderpane */
BorderPane root = new BorderPane();
root.setPadding(new Insets(15));
root.setLeft(leftCol);
root.setRight(rightCol);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
The output it gives is shown in the image below:
However, I want it to look more like this:
Where the left and right columns are equal width and take up the entire width of the window. Additionally, the columns do not change width with the window, so the whitespace in the middle gets bigger as the window gets bigger.
What do I need to change to make the columns fill the width of the window?
(P.S. I'm still learning, so if the solution could avoid FXML (which I don't understand yet), that'd be great)
EDIT: as per #k88's suggestion, my start method now looks like so:
public void start(Stage stage) throws Exception {
String blackBorder = "-fx-border-style: solid; -fx-border-width: 1; -fx-border-color: black";
Button calculate = new Button("Calculate");
Button cancel = new Button("Cancel");
HBox runButtons = new HBox(calculate, cancel);
VBox rightCol = new VBox(runButtons);
rightCol.setStyle(blackBorder);
Button save = new Button("Save");
Button del= new Button("Delete");
HBox settings = new HBox(save, load);
VBox leftCol = new VBox(settings);
leftCol.setStyle(blackBorder);
HBox root = new HBox(leftCol, rightCol);
root.setPadding(new Insets(15));
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
stage.show();
}
Giving a window looking like:
There are different ways to get this problem fixed.
If you want to still gain the benefits from BorderPane (like to have top and bottom panes), you can set a HBox/GridPane as the center (without setting left/right).
If you are not bothered about top and bottom layout implementations, then as #k88 suggested, you can use directly HBox or GridPane as your root node.
Using HBox:
HBox.setHGrow(leftCol,Priority.ALWAYS);
HBox.setHGrow(rightCol,Priority.ALWAYS);
HBox root = new HBox();
root.setPadding(new Insets(15));
root.getChildren().addAll(leftCol, rightCol);
Using GridPane:
GridPane root = new GridPane();
ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(50);
ColumnConstraints col2 = new ColumnConstraints();
col2.setPercentWidth(50);
root.getColumnConstraints().addAll(col1,col2);
root.addRow(0, leftCol,rightCol);
Update: In either cases, if you want your buttons to auto stretch, bind the width of the buttons to its layout. This way you can control the buttons width proportion in the HBox.
Button calculate = new Button("Calculate");
Button cancel = new Button("Cancel");
HBox runButtons = new HBox(calculate, cancel);
calculate.prefWidthProperty().bind(runButtons.widthProperty().divide(2));
cancel.prefWidthProperty().bind(runButtons.widthProperty().divide(2));
Update 2: Please find below a sample demo.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class Sample extends Application {
public void start(Stage stage) throws Exception {
String blackBorder = "-fx-border-style: solid; -fx-border-width: 1; -fx-border-color: black";
Button calculate = new Button("Calculate");
Button cancel = new Button("Cancel");
HBox runButtons = new HBox(calculate, cancel);
calculate.prefWidthProperty().bind(runButtons.widthProperty().divide(2));
cancel.prefWidthProperty().bind(runButtons.widthProperty().divide(2));
VBox rightCol = new VBox(runButtons);
rightCol.setStyle(blackBorder);
Button save = new Button("Save");
Button del = new Button("Delete");
HBox settings = new HBox(save, del);
save.prefWidthProperty().bind(settings.widthProperty().divide(3)); // 1/3
del.prefWidthProperty().bind(settings.widthProperty().divide(3).multiply(2)); // 2/3
VBox leftCol = new VBox(settings);
leftCol.setStyle(blackBorder);
GridPane root = new GridPane();
ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(50);
ColumnConstraints col2 = new ColumnConstraints();
col2.setPercentWidth(50);
root.getColumnConstraints().addAll(col1,col2);
root.addRow(0, leftCol,rightCol);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String... a) {
Application.launch(a);
}
}
I am trying to use JavaFX to create a scene with the program's title positioned at the top-center, and buttons in a vertical line along the left side of the scene. However, both of these elements are displayed clustered up in the top-right of the scene, instead of where I want them to be.
How can I get these elements to be displayed where I want them to?
Here is how I try to set the program title's position:
grid.add(gameTitle, 0, 0);
GridPane.setHalignment(gameTitle, HPos.CENTER);
GridPane.setValignment(gameTitle, VPos.TOP);
I try to set the VBox object similarly:
grid.getChildren().add(buttonBox);
GridPane.setHalignment(buttonBox, HPos.LEFT);
GridPane.setValignment(buttonBox, VPos.CENTER);
This is what is displayed:
My entire MainMenu class. (This class is called in my Main class to construct the scene):
package scenes;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.application.Platform;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class MainMenu {
public Pane getMainMenuPane() {
// Create the scene grid
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
// Set the game title to the top center
Text gameTitle = new Text("Bandit King");
Font titleFont = new Font(75);
gameTitle.setFont(titleFont);
//
grid.add(gameTitle, 0, 0);
GridPane.setHalignment(gameTitle, HPos.CENTER);
GridPane.setValignment(gameTitle, VPos.TOP);
// Create Button objects and put in VBox
Button[] buttArr = makeButtons();
VBox buttonBox = new VBox();
buttonBox.getChildren().addAll(buttArr);
buttonBox.setSpacing(10);
// add Button VBox to GridPane
grid.getChildren().add(buttonBox);
GridPane.setHalignment(buttonBox, HPos.LEFT);
GridPane.setValignment(buttonBox, VPos.CENTER);
return (Pane) grid;
}
private Button[] makeButtons() {
// Create buttons
Button start = new Button("Start a New Game");
Button load = new Button("Load a Saved Game");
Button exit = new Button("Exit the Game");
// set Button actions
start.setOnAction( a -> {
System.out.println("WIP- start game.");
});
load.setOnAction( a -> {
System.out.println("WIP- load game");
});
exit.setOnAction( a -> {
Platform.exit();
System.exit(0);
});
// return Button[] array
Button[] buttArr = {start, load, exit};
return buttArr;
}
}
My Main class (Displays the scene):
package central;
import javafx.stage.Stage;
import scenes.*;
import controllers.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
public class Main extends Application {
// Get scene panes
private static Pane mainMenu = new MainMenu().getMainMenuPane();
// Create SceneController object.
private static Scene scene = new Scene(mainMenu, 1600, 900);
public static SceneController SceneControl = new SceneController(scene);
#Override
public void start(Stage stage) {
stage.setTitle("Bandit King");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The default cell you add the children of a GridPane to is (0, 0) which is what you do in this line:
grid.getChildren().add(buttonBox);
you need to change this to
grid.add(buttonBox, 0, 1);
to set the row index to 1. (There are alternatives to assigning the row index this way, but this is the most convenient option in this case.)
This won't result in the first column taking the full width of the GridPane though. If you also want the first column to take all the width available, you need to specify this by adding ColumnConstraints:
ColumnConstraints constraints = new ColumnConstraints();
constraints.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().add(constraints);
As far as what I noticed, you added all the nodes in a column and set there positions, but you did not specify how much the column needs to be stretched. GridPane column will not stretch automatically by itself unless specified.
You can debug your program, by enabling the gridLinesVisible of GridPane property to true.
grid.setGridLinesVisible(true);
You need to specify the columnConstraints, to let the GridPane column stretch to the available width.
ColumnConstraints constraint = new ColumnConstraints();
constraint.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().add(constraint);
I have three VBoxes in a HBox. I want all of them to always take one third of the HBox and the full height. I've tried HBox.setHgrow(<every VBox>, Priority.ALWAYS) with <every VBox>.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); and that worked fine, but when I added a component to one of the VBoxes, it resized itself and became larger than the other ones.
Any idea how to solve this properly?
Use a GridPane instead of the HBox. You can use a collection of column constraints, each with the percentWidth set to give each column equal width.
SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class VBoxInGridPane extends Application {
#Override
public void start(Stage primaryStage) {
VBox box1 = new VBox();
box1.setStyle("-fx-background-color: -fx-background; -fx-background: red ;");
box1.getChildren().add(new Label("Content"));
VBox box2 = new VBox();
box2.setStyle("-fx-background-color: green ;");
VBox box3 = new VBox();
box3.setStyle("-fx-background-color: blue ;");
GridPane root = new GridPane();
root.add(box1, 0, 0);
root.add(box2, 1, 0);
root.add(box3, 2, 0);
for (int i = 0 ; i < 3 ; i++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setPercentWidth(100.0/3.0);
cc.setHgrow(Priority.ALWAYS);
root.getColumnConstraints().add(cc);
}
RowConstraints rc = new RowConstraints();
rc.setVgrow(Priority.ALWAYS);
root.getRowConstraints().add(rc);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
What you could do is to add your VBox to a StackPane and the StackPane to the HBox. Into the StackPane you also place a placeholder (I usually use a transparent Rectangular) and bind that to the binding maxVBoxWidth. This is a Binding that you have to define yourself:
DoubleBinding maxVBoxBinding = new DoubleBinding() {
{
super.bind(vbox1.widthProperty(),vbox2.widthProperty(), vbox3.widthProperty());
}
#Override
protected double computeValue() {
return Math.max(vbox1.getWidth(), Math.max(vbox2.getWidth(), vbox2.getWidth()));
}
}
package example;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Text text = new Text("This is a Text");
VBox box = new VBox();
box.setAlignment(Pos.CENTER);
box.setStyle("-fx-background-color: yellow;");
box.getChildren().add(text);
StackPane container = new StackPane();
container.getChildren().add(box);
BorderPane bp = new BorderPane();
bp.setCenter(container);
Scene scene = new Scene(bp, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Here's the output:
Question: Can someone explain to me why the Vbox fill the whole screen? Is there a method that is similar to Android's wrap_content? I want the image below to be the output:
Solution
Wrap the VBox in a Group; e.g. use:
container.getChildren().add(new Group(box));
instead of:
container.getChildren().add(box);
Why it works
From the Group javadoc:
By default, a Group will "auto-size" its managed resizable children to their preferred sizes during the layout pass.
This means that the VBox won't grow past the preferred size of it's content (which is just enough area to display the label inside it).
Alternate implementation
Set the maximum size of the VBox to the preferred size. Then the VBox will only ever grow large enough to fit the preferred size of the content inside it and will never grow any larger.
box.setMaxSize(VBox.USE_PREF_SIZE, VBox.USE_PREF_SIZE);
Why VBox grows by default
It is a resizable container which will stretch to fill available area.
Note
I don't know that the effect is exactly the same as an Android wrap_content method as I have never developed for Android, however the effect does seem to exactly match the second image you provided in your question, which appears to be what you want.
VBox automatically resizes itself to the size of the Parent, so it is better not to set background color to it. Instead, you can use a Label in place of a Text and then add background color to the Label instead of the VBox.
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Label text = new Label("This is a Text");
VBox box = new VBox();
box.setAlignment(Pos.CENTER);
text.setStyle("-fx-background-color: yellow;");
box.getChildren().add(text);
StackPane container = new StackPane();
container.getChildren().add(box);
BorderPane bp = new BorderPane();
bp.setCenter(container);
Scene scene = new Scene(bp, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
This will give you an output like an image below: