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()));
}
}
Related
I'm new to JavaFX, trying to build a GUI program that displays a bill for a table at a restaurant when you click on that table. The spacing is off between the table buttons and I'm not sure why.
The GUI class for my program:
package restaurantBillingProgram;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.geometry.Pos;
public class BillingGUI extends Application {
#Override
public void start(Stage primaryStage) {
// Create grid pane
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setHgap(5);
pane.setVgap(5);
// Label
pane.add(new Label("Generate bill"), 1, 0);
// Buttons
Button btT1 = new Button("Table 1");
pane.add(btT1, 0, 1);
btT1.setOnAction(e - > Billing.generateT1());
Button btT2 = new Button("Table 2");
pane.add(btT2, 1, 1);
btT2.setOnAction(e - > Billing.generateT2());
Button btT3 = new Button("Table 3");
pane.add(btT3, 2, 1);
btT3.setOnAction(e - > Billing.generateT3());
// Create scene and place in stage
Scene scene = new Scene(pane, 250, 250);
primaryStage.setTitle("Restaurant Billing Program");
primaryStage.setScene(scene);
primaryStage.show();
}
// Main method
public static void main(String[] args) {
launch(args);
}
}
From the Javadoc:
Row/Column Sizing
By default, rows and columns will be sized to fit their content; a column will be wide enough to accommodate the widest child, ...
The label in row 0 column 1 forces that column to be wider.
You probably want the label to be centered and span all 3 columns.
While doing you layout, use pane.setGridLinesVisible(true). This should only be used during debugging. It can be very useful for situations like your current situation. As #Jim Garrison pointed out, your Label is causing the issue:
Issue:
One way to fix this is to let the Label span all columns and center the Label's text.
Fix:
Key Code:
label.setMaxWidth(Double.MAX_VALUE);
label.setAlignment(Pos.CENTER);
pane.add(label, 0, 0, 3, 1);// Look at the following link to see how this add method works. https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/layout/GridPane.html#add(javafx.scene.Node,int,int,int,int)
Full Code:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.geometry.Pos;
public class BillingGUI extends Application {
#Override
public void start(Stage primaryStage) {
// Create grid pane
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setHgap(5);
pane.setVgap(5);
pane.setGridLinesVisible(true);//Use for debugging only!!!!
// Label
Label label = new Label("Generate bill");
label.setMaxWidth(Double.MAX_VALUE);
label.setAlignment(Pos.CENTER);
pane.add(label, 0, 0, 3, 1);
// Buttons
Button btT1 = new Button("Table 1");
pane.add(btT1, 0, 1);
Button btT2 = new Button("Table 2");
pane.add(btT2, 1, 1);
Button btT3 = new Button("Table 3");
pane.add(btT3, 2, 1);
// Create scene and place in stage
Scene scene = new Scene(pane, 250, 250);
primaryStage.setTitle("Restaurant Billing Program");
primaryStage.setScene(scene);
primaryStage.show();
}
// Main method
public static void main(String[] args) {
launch(args);
}
}
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);
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);
Currently the code below produces a BorderPane with a GridPane in the center and a HBox on the bottom to hold two buttons. The left-most pane in the GridPane contains the text "Name Here". Right now I only want the buttons to move the text "Name Here" up and down but they will not move the text.
I think it has something to do with the particular GridPane node, but I'm not sure. Additionally, I don't know why the left-most GridPane takes up more space relative to the right-most GridPane within the center of the BorderPane.
Any advice will be greatly appreciated, thank you!
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.geometry.Pos;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.Priority;
import javafx.scene.text.Text;
public class differentWindows extends Application {
protected Text name = new Text("Name Here");
protected BorderPane getPane() {
// HBox to hold the up and down buttons
HBox paneForButtons = new HBox(20);
Button btUp = new Button("Up");
Button btDown = new Button("Down");
paneForButtons.getChildren().addAll(btUp, btDown);
paneForButtons.setAlignment(Pos.BOTTOM_LEFT);
// Grid pane to go in center of the border pane, for the name and video
GridPane paneForTextNVideo = new GridPane();
paneForTextNVideo.setAlignment(Pos.CENTER);
paneForTextNVideo.setGridLinesVisible(true);
paneForTextNVideo.add(name, 0, 0);
Text temp = new Text("temp");
paneForTextNVideo.add(temp, 1, 0);
paneForTextNVideo.setHalignment(temp, HPos.CENTER);
paneForTextNVideo.setValignment(temp, VPos.CENTER);
paneForTextNVideo.setHgrow(temp, Priority.ALWAYS);
paneForTextNVideo.setVgrow(temp, Priority.ALWAYS);
paneForTextNVideo.setHalignment(name, HPos.CENTER);
paneForTextNVideo.setValignment(name, VPos.CENTER);
paneForTextNVideo.setHgrow(name, Priority.ALWAYS);
paneForTextNVideo.setVgrow(name, Priority.ALWAYS);
// Border pane to hold all windows
BorderPane pane = new BorderPane();
pane.setBottom(paneForButtons);
pane.setCenter(paneForTextNVideo);
btUp.setOnAction(e -> name.setY(name.getY() - 10));
btDown.setOnAction(e -> name.setY(name.getY() + 10));
return pane;
} // end of the getPane method
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(getPane(), 450, 200);
primaryStage.setTitle("Assignment #7");
primaryStage.setScene(scene);
primaryStage.show();
} // end of start method
public static void main(String[] args) {
Application.launch(args);
}
} // end of class
Try using setLayoutY instead of setY:
btUp.setOnAction(e -> name.setLayoutY(name.getLayoutY() - 10));
btDown.setOnAction(e -> name.setLayoutY(name.getLayoutY() + 10));
As a sidenote, the Node parent class also has a relocate method for easily changing both the X and Y coordinates: