JavaFX: Making BorderPane's Center's contents not strech - javafx

just a quick one for you since I'm generally inexperienced in designing apps. Any help would be greatly appreciated.
I've got the following application, and it's obvious that it uses a BorderPane for its layout. I've got a GridPane of 5 labels and 5 textFields in the Center region, and I wanted a subtle border for it.
As you can see, the GridPane utilizes the full space allocated to it in the Center region, and although the border gets drawn nicely, it goes all the way down to the bottom of the Center region (Red arrows).
What I want it to do is finish at the blue line.
I tried using grid.setPrefHeight(400);, for example, but it didn't work.
Are there any solutions other than the obvious one, to add a second container below the GridPane and squeeze the upper container enough?
EDIT: For reference, this is the code that creates the center area GridPane:
public GridPane addGridPaneCenter() {
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(0, 10, 0, 10));
sihuid = new Text("SIHU ID:");
sihuid.setFont(Font.font("Inconsolata", 16));
grid.add(sihuid, 0, 1); //spans from 0,1 to 1,1 (Column-Row)
sihuid_tf = new TextField();
sihuid_tf.setEditable(false);
grid.add(sihuid_tf, 1, 1);
mac = new Text("Plug MAC:");
mac.setFont(Font.font("Inconsolata", 16));
grid.add(mac, 0, 2);
mac_tf = new TextField();
mac_tf.setEditable(false);
grid.add(mac_tf, 1, 2);
loc = new Text("Location:");
loc.setFont(Font.font("Inconsolata", 16));
grid.add(loc, 0, 3);
loc_tf = new TextField();
loc_tf.setEditable(false);
grid.add(loc_tf, 1, 3);
appl = new Text("Appliance:");
appl.setFont(Font.font("Inconsolata", 16));
grid.add(appl, 0, 4);
appl_tf = new TextField();
appl_tf.setEditable(false);
grid.add(appl_tf, 1, 4);
type = new Text("Type:");
type.setFont(Font.font("Inconsolata", 16));
grid.add(type, 0, 5);
type_tf = new TextField();
type_tf.setEditable(false);
grid.add(type_tf, 1, 5);
grid.setPrefHeight(400);
grid.setStyle(
"-fx-border-color: #b8b8ba; -fx-border-width: 1;"
+ "-fx-border-radius: 4;"
/*+ "-fx-font: " + "Inconsolata" + ";" */
);
return grid;
}
After that, there's a simple
GridPane grid_center = addGridPaneCenter(); //CENTER Grid, contains info about plugs.
border_pane.setCenter(grid_center);
Scene scene = new Scene(border_pane, 900, 700);
scene.setFill(Color.GHOSTWHITE);
primaryStage.setTitle("PlugControl v0.1e");
primaryStage.setScene(scene);
primaryStage.show();

I'm afraid this is behaviour of BorderPane is by design (ref):
The top and bottom children will be resized to their preferred heights and extend the width of the borderpane. 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.
You should probably put the GridPane in an AnchorPane or VBox and put that (AnchorPane or VBox) in the center of the BorderPane.

Related

JavaFX Layout issue within hbox

I want to add an statusbar to my application (the root pane is a vbox and the statusbar is a hbox with a fix height). On this statusbar I have a label with something like "2 processes running". As soon as the mouse hoovers this label, I want to add a Pane above this label with some details about the processes (like IntelliJ or Eclipse).
My problem in the moment is how to create this pane and position this pane above the label.
I create a simple example of my problem
The green area is a StackPane on the root VBox with VGow = Always. The red area is the Hbox with a fix height of 30 pixel. Then I added the yellow VBox the the HBox and put the minHeight to 300.
The problem is, that the yellow area should be above the red area (over the green area) and not outside the window.
What is the best way to achive something like that?
The only way I figured out was using a negative top margin amount (- max/min height). But then the window gets stretched because the yellow pane (or the green bordered pane is not on top of the other elements). The red crossed area shouldn't be there. The green boxed area should be above the other content. Probably I can't use a vbox as my root element?
Update 1
Here is an example - strange thing is, that it is working in this standalone example. But is that the way I should do something like that?
VBox rootBox = new VBox();
rootBox.setMaxHeight(500);
rootBox.setStyle("-fx-background-color: lightgreen");
StackPane contentPane = new StackPane();
contentPane.getChildren().add(new Button("Dont click me"));
contentPane.setStyle("-fx-background-color: lightblue");
VBox.setVgrow(contentPane, Priority.ALWAYS);
HBox statusbar = new HBox();
statusbar.setMinHeight(30);
statusbar.setMaxHeight(30);
statusbar.setStyle("-fx-background-color: red");
VBox processIndicatorBox = new VBox();
processIndicatorBox.setMinHeight(30);
processIndicatorBox.setMaxHeight(30);
HBox.setMargin(processIndicatorBox, new Insets(-300, 0, 0, 0));
StackPane processListPane = new StackPane();
processListPane.setStyle("-fx-background-color: yellow");
processListPane.setMinHeight(300);
processListPane.setMaxHeight(300);
processListPane.setMinWidth(150);
processListPane.setMaxWidth(150);
processListPane.setVisible(false);
Label label = new Label("Show processes");
label.setOnMouseEntered(mouseEvent -> processListPane.setVisible(true));
label.setOnMouseExited(mouseEvent -> processListPane.setVisible(false));
processIndicatorBox.getChildren().addAll(processListPane, label);
statusbar.getChildren().add(processIndicatorBox);
rootBox.getChildren().addAll(contentPane, statusbar);
Scene scene = new Scene(rootBox, 600, 500);
primaryStage.setScene(scene);
primaryStage.show();
Many greetings
Hauke
Did you consider using GridPane? ( https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/GridPane.html ). Gives you more control to layout elements with different sizes. VBox and HBox are from my experience good when all elements have similar sizes.
Here a pseudo-pseudo code
GridPane grid = new GridPane();
Pane greenBox = new Pane(); //your green box
Pane redBox = new Pane(); //your red box
Pane yellowBox = new Pane(); //your yellow box
// add the panes to the grid pane and define where they are
grid.add(greenBox, 0, 0); set the green box in column 0 and row 0
grid.add(redBox, 0, 1); set the red box in column 0 and row 1
grid.add(yellowBox, 1, 1); set the yellowbox in column 1 and row 1
// size of the boxes
GridPane.setColumnSpan(greenBox,2); //The green box should span over 2 columns
GridPane.setRowSpan(yellowBox,2); //The red box should span over 2 rows
The boxes are now only empty panes, which will have a minimum size without content. Replace the Pane() boxes with the content you want to put in or put the content in the Pane() objects.

Region width not refreshing automatically after modifying Grid Pane to which the region width is bound - JavaFX

I'm making a simple Java GUI app using JavaFX that has a Border Pane as the root node.
In the top section of the Border Pane, there is a Grid Pane with three columns (top Grid Pane from now on).
In the first column of the top Grid Pane, there is a Home Button, in the second column, there is an empty Region that only serves as spacer between the first and third column of the top Grid Pane, and in the third column, there is another GridPane (right Grid Pane from now on).
The right Grid Pane contains one Button (Log In Button) on start. However, when a user successfully logs into the app, two other Buttons and a Label are added to the right Grid Pane as part of the Log In Button click event.
The spacer maxWidthProperty and minWidthProperty are bound to the top Grid Pane (tgp) widthProperty and the right Grid Pane(rgp) widthProperty like this:
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
which makes the right Grid Pane move nicely with its buttons staying on the right side of the scene when a user resizes the main stage.
However, a problem occurs when the user logs in and additional buttons are added to the right Grid Pane. The spacer somehow misses this change and its width stays the same, which makes the additional Buttons appear outside of the current stage width. The only way to refresh the spacer width is to interact with the stage somehow, by clicking minimize/maximize/restore or by clicking any button on the scene.
Is there a way to automatically refresh Region width after the nodes to which its width is bound to are modified? Or, is there a better approach to making a top Grid Pane with one button on the left and modifiable number of buttons (nodes) on the right?
Edit: Here is a demonstration of the problem with several screenshots stacked on one another:
Minimal reproducible example:
BorderPane root = new BorderPane();
GridPane tgp = new GridPane();
tgp.minWidthProperty().bind(root.widthProperty());
tgp.maxWidthProperty().bind(root.widthProperty());
tgp.setStyle("-fx-background-color: WHITE; -fx-border-color: LIGHTGREY;");
tgp.setMinHeight(37);
tgp.setMaxHeight(37);
root.setTop(tgp);
Button homeButton = new Button("Home"));
homeButton.setMinHeight(35);
homeButton.setMaxHeight(35);
homeButton.setMinWidth(80);
homeButton.setMaxWidth(80);
tgp.add(homeButton, 0, 0);
GridPane rgp = new GridPane(); // Right Grid Pane - holds User related nodes
rgp.setHgap(5);
tgp.add(rgp, 2, 0);
Label unl = new Label("My Profile");
unl.setFont(new Font("Calibri", 15));
unl.setTextFill(Color.RED);
unl.setMinWidth(Region.USE_PREF_SIZE);
Button wlButton = new Button("Watchlist");
wlButton.setMinHeight(35);
wlButton.setMaxHeight(35);
wlButton.setMinWidth(80);
wlButton.setMaxWidth(80);
Button cartButton = new Button("Cart");
cartButton.setMinHeight(35);
cartButton.setMaxHeight(35);
cartButton.setMinWidth(60);
cartButton.setMaxWidth(60);
Button logInOutButton = new Button("Log In");
logInOutButton.setMinHeight(35);
logInOutButton.setMaxHeight(35);
logInOutButton.setMinWidth(60);
logInOutButton.setMaxWidth(60);
rgp.add(logInOutButton, 3, 0);
logInOutButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (logInOutButton.getText().equals("Log In")) {
LogInStage lis = new LogInStage();
lis.initStage();
if (lis.username != null) {
logInOutButton.setText("Log Out");
rgp.add(unl, 0, 0);
rgp.add(wlButton, 1, 0);
rgp.add(cartButton, 2, 0);
}
} else if (logInOutButton.getText().equals("Log Out")) {
logInOutButton.setText("Log In");
rgp.getChildren().remove(unl);
rgp.getChildren().remove(wlButton);
rgp.getChildren().remove(cartButton);
}
}
});
Region spacer = new Region();
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
tgp.add(spacer, 1, 0)
It's always a bad idea to use bindings, if you can avoid it. Any changes to the size constraints can lead to a new layout pass being scheduled, but during the layout pass they are assumed to be constant. If you now introduce a binding the following sequence of events could happen:
A layout pass is requested for the GridPane, setting a flag to indicate layout is required
A layout pass happens. During the layout pass the children are resized. This triggers an update of the constraints of the children with the bindings.
The flag is cleared, but the changes to the contraints already happened. The layout won't reflect this. The GridPane gets another reason to do a layout.
I don't know, how your scene is set up in detail, but I recommend using column constraints: Set the grow priorities for the outer ones to SOMETIMES and the one for the center to ALWAYS. If you require some spacing around the children, you could use GridPane.setMargin (or the padding of the GridPane itself, if you require the a distance to the edges for all children).
#Override
public void start(Stage primaryStage) {
Button[] rightContent = new Button[3];
for (int i = 0; i < rightContent.length; i++) {
Button btn = new Button(Integer.toString(i));
GridPane.setColumnIndex(btn, i);
rightContent[i] = btn;
}
Button cycle = new Button("cycle");
GridPane rgp = new GridPane(); // I would usually use a HBox here
// don't grow larger than needed
rgp.setMaxWidth(Region.USE_PREF_SIZE);
// cycle though 0 to 3 buttons on the right
cycle.setOnAction(new EventHandler<ActionEvent>() {
int nextIndex = 0;
#Override
public void handle(ActionEvent event) {
if (nextIndex >= rightContent.length) {
rgp.getChildren().clear();
nextIndex = 0;
} else {
rgp.getChildren().add(rightContent[nextIndex]);
nextIndex++;
}
}
});
ColumnConstraints sideConstraints = new ColumnConstraints();
sideConstraints.setHgrow(Priority.SOMETIMES);
ColumnConstraints centerConstraints = new ColumnConstraints();
centerConstraints.setHgrow(Priority.ALWAYS);
//prefer to grow the center part of the GridPane
GridPane root = new GridPane();
root.getColumnConstraints().addAll(sideConstraints, centerConstraints, sideConstraints);
root.add(cycle, 0, 0);
root.add(rgp, 2, 0);
// add something to visualize the center part
// you could simply leave this part out
Region center = new Region();
center.setStyle("-fx-border-radius: 10;-fx-border-width: 1;-fx-border-color:black;");
root.add(center, 1, 0);
Scene scene = new Scene(root, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
As mentioned in the comments, the center region is not actually needed.

Add a node in the middle of a line java fx

I have a line and I want a square with text inside of the square to be placed in the middle of this line.
I have created the square with text using a stack pane. This line is draggable so I want the square to stay in the middle of this line when it is being dragged.
I tried:
weightSquare.layoutXProperty().bind((line.startXProperty().add(line.endXProperty())).divide(2).add(line.translateXProperty()));
weightSquare.layoutYProperty().bind((line.startYProperty().add(line.endYProperty())).divide(2).add(line.translateXProperty()));
where weightSquare is a StackPane containing a rectangle and text.
Currently, the weightSquare is near the middle of the line but not perfectly in the middle. When the line moves around the weightSquare stays relatively near the middle of the line but sometimes goes off the line slightly.
I want something like this:
Example of what I want
Thank you.
Assuming no transformations have been applied to the line or the StackPane, you can calculate the position of the StackPane based on the line properties like this
stackPane.layoutX = (line.startX + line.endX - stackPane.width) / 2;
(Procede accordingly for y coordinates.)
transformX and transformY could simply be added, but general transforms would require you to
Listen to changes of the transforms
Use localToParent on the start/end coordinates of the line to get the location in the parent.
I recommend using Bindings.createDoubleBindings for complicate double bindings btw, since this makes the formula for calculating the values much easier to read.
Example
I use a Label, since this provides background/border functionality too.
#Override
public void start(Stage primaryStage) throws IOException {
Label label = new Label();
label.setStyle("-fx-background-color: white; -fx-border-color: black;");
label.setPadding(new Insets(2, 4, 2, 4));
Line line = new Line(300, 300, 300, 100);
label.layoutXProperty().bind(Bindings.createDoubleBinding(
() -> (line.getStartX() + line.getEndX() - label.getWidth()) / 2,
line.startXProperty(), line.endXProperty(), label.widthProperty()));
label.layoutYProperty().bind(Bindings.createDoubleBinding(
() -> (line.getStartY() + line.getEndY() - label.getHeight()) / 2,
line.startYProperty(), line.endYProperty(), label.heightProperty()));
DoubleProperty angle = new SimpleDoubleProperty();
line.endXProperty().bind(Bindings.createDoubleBinding(() -> 300 + 200 * Math.sin(angle.get()), angle));
line.endYProperty().bind(Bindings.createDoubleBinding(() -> 300 + 200 * Math.cos(angle.get()), angle));
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(angle, 0d, Interpolator.LINEAR)),
new KeyFrame(Duration.seconds(10), new KeyValue(angle, Math.PI * 2, Interpolator.LINEAR)));
timeline.setCycleCount(Animation.INDEFINITE);
label.textProperty().bind(timeline.currentTimeProperty().asString());
timeline.play();
Scene scene = new Scene(new Pane(line, label), 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}

Align a label with textarea

How can I align the position of my "Description" label such that it corresponds in line with the "Enter description" text (Label - TextArea). These components are inside an gridPane and coded in javaFX ( so no Scene Builder tips can help me here ).
Code:
Label descriptionLabel = new Label("Description:");
descriptionLabel.setPadding(new Insets(5, 5, 5, 5));
JFXTextArea descriptionTextArea = new JFXTextArea();
descriptionTextArea.setPadding(new Insets(5, 0, 5, 0));
descriptionTextArea.setPromptText("Enter description...");
gridPane.add(descriptionLabel, 0, 2);
gridPane.add(descriptionTextArea, 1, 2);
I've tried with descriptionLabel.setAlignment(Pos.TOP_LEFT); but neither that helped me out
You have to use GridPane Constraints. Valignment set to Top and Halignment set to Right. Java Doc
GridPane.setHalignment(descriptionLabel, HPos.RIGHT);
GridPane.setValignment(descriptionLabel, VPos.TOP);
After looking at you picture again, I think you only need setValignment.

Fill/Padding space between 2 Labels with dots in GridPane (JavaFX)

Starting point:
GridPane with 2 Columns
each Column has a Label
Like-to-have output:
space between the labels filled by dots
So far I have only come across String solutions in which the target length of the combined String is known. But this case doesn't do it for me because I need a solution which can also works when screen size changes and therefore the space between Labels do change dynamically. Could you guys please point me to the right direction?
You could put the 2 Labels in a HBox with a Region in between them, set hgrow for the labels and the Region to NEVER and ALWAYS respectively and use a linear gradient as background for the region that draws half of it's size black and the other half transparent.
Example
// 20 px wide horizontal gradient alternating between black and transparent with immediate color switch
private static final Paint FILL = new LinearGradient(
0, 0,
10, 0,
false,
CycleMethod.REPEAT,
new Stop(0, Color.BLACK),
new Stop(0.5, Color.BLACK),
new Stop(0.5, Color.TRANSPARENT)
);
// create background for regions
private static final Background BACKGROUND = new Background(new BackgroundFill(FILL, CornerRadii.EMPTY, Insets.EMPTY));
private static void addRow(Pane parent, String s1, String s2) {
// create labels
Label label1 = new Label(s2);
Label label2 = new Label('['+s2+']');
// create filler region with "stroke width" 2
Region filler = new Region();
filler.setPrefHeight(2);
filler.setBackground(BACKGROUND);
HBox hbox = new HBox(5, label1, filler, label2);
hbox.setAlignment(Pos.CENTER);
HBox.setHgrow(label1, Priority.NEVER);
HBox.setHgrow(label2, Priority.NEVER);
HBox.setHgrow(filler, Priority.ALWAYS);
hbox.setFillHeight(false);
parent.getChildren().add(hbox);
}
#Override
public void start(Stage primaryStage) {
VBox root = new VBox();
addRow(root, "JBoss", "DOWN");
addRow(root, "GlassFish", "UP");
addRow(root, "verylongprocessname", "UP");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
You could also use a border (top only) on the Region instead of using a background and do not set prefHeight. This would allow you to actually use dots instead of strokes, but since your picture shows strokes, I posted the background approach instead...

Resources