How do I fit a JavaFX Pane to the size of its contents? - javafx

In JavaFX, I see lots of examples of how to make a child component extend its size to fit the parent pane. But I can't see how to shrink the parent pane to fit the size of its child contents.
In the following example, I create a Stage with a Scene of size 500x500 pixels. Inside that Scene is a VBox that has one child, a single Label.
I'd like the VBox Pane to be the size of the Label, not the size of the whole Stage. (In a more complex application, I'm making the VBox a draggable Pane, so I want it to be just big enough to fit its contents).
How can I do that?
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox vbox = new VBox();
vbox.setStyle("-fx-background-color: #efecc2");
vbox.setPrefSize(100, 100);
Label label = new Label("Label");
label.setStyle("-fx-background-color: lightcyan");
vbox.getChildren().add(label);
Scene scene = new Scene(vbox, 500, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
As you can see in the picture below, the label (with a blue background) is small, but the VBox (with a yellow background) fills the whole window. It doesn't seem to matter that I set the preferred size of the VBox to 100,100: it still fills up the whole 500 x 300 pixel Scene.
How can I tell the VBox to be only as big as the Label that is inside of it? (Or, when I add, say, 3 things inside it, to be as big as those?)

First problem here is a scene's root object. It will always be the same size as the scene. So you need to add the parent Pane as root element and then add VBox into it.
Second problem is a type of Pane. Some elements affect the size of children(BorderPane, StackPane), some not (Pane, AnchorPane). So you need to choose the right parent.
Here is a simple example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Sample extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label("Label");
label.setStyle("-fx-background-color: lightcyan");
VBox vBox = new VBox();
vBox.setStyle("-fx-background-color: #efecc2");
vBox.getChildren().add(label);
AnchorPane root = new AnchorPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 500, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

JavaFX: Determine Bounds of a node while being invisible?

is there any way to determine the bounds (especially height and width) of a node which is already attached to a scene but set to invisible?
I want to show a label on screen only if its width exceeds 100px... but it is always 0:
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Scene scene = new Scene(root, 500, 500, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
Label n = new Label();
n.setVisible(false);
n.setStyle("-fx-background-color: red;");
root.getChildren()
.addAll(n);
n.textProperty()
.addListener((v, ov, nv) -> {
System.out.println(n.getBoundsInParent());
n.setVisible(n.getWidth() > 100);
});
n.setText("TEST11111111111111111111111");
}
The result of the sysout: (also n.getWidth() is no better)
BoundingBox [minX:0.0, minY:0.0, minZ:0.0, width:0.0, height:0.0, depth:0.0, maxX:0.0, maxY:0.0, maxZ:0.0]
Is there any trick ?
Thanks all!
Your problem is that you are listening for changes to the text property and expecting the width of the node to be updated at that time - but it's not. The width of nodes are only calculated and set during a render pass which consists of an applyCSS and layout routine (see: Get the height of a node in JavaFX (generate a layout pass)). Your code incorrectly sets the node to invisible before the updated size of the node is calculated.
Instead of using a listener on the text property to determine visibility of the node, I suggest that you use a binding expression to create a direct binding on the visibility property to the desired width property. An example of this approach is provided below. You can see that the label only displays when the text to display is longer than the required width (in this case 100 pixels).
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class BoundSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Scene scene = new Scene(root, 200, 100);
primaryStage.setScene(scene);
primaryStage.show();
Label n = new Label();
n.setVisible(false);
n.visibleProperty().bind(n.widthProperty().greaterThan(100));
TextField textField = new TextField("TEST11111111111111111111111");
n.textProperty().bind(textField.textProperty());
textField.relocate(0, 50);
root.getChildren().addAll(n, textField);
}
}

JavaFX setOnShown fires before window is visible

I'm trying to set the minimum height of a window to the height of the scene it contains + the title bar height (so after showing the window, there's no possibility to shrink it more). Following this answer I wrote:
stage.setOnShown(event -> {
stage.setMinHeight(stage.getHeight());
});
It doesn't seem to work though because when the event is fired the window is not even shown on screen and its height is still equal to the height of the scene (title bar is not taken into consideration). My question is then - how to run the code when window is actually visible on the screen?
You can force the root of the scene to be laid out, and then size the stage to the scene's size:
stage.setOnShown(e -> {
stage.getScene().getRoot().layout();
stage.sizeToScene();
stage.setMinHeight(stage.getHeight());
});
Here's a SSCCE:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class StageSizeTest extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setOnShown(e -> {
primaryStage.getScene().getRoot().layout();
primaryStage.sizeToScene();
System.out.printf("[%.1f, %.1f]%n", primaryStage.getWidth(), primaryStage.getHeight());
primaryStage.setMinWidth(primaryStage.getWidth());
primaryStage.setMinHeight(primaryStage.getHeight());
});
VBox root = new VBox(10,
new Label("Test one"),
new Label("Another test"),
new Label("Another really long label to give the scene some width"),
new Label("Bottom label"));
root.setPadding(new Insets(10));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX ScrollPane and Scaling of the Content

I would like to show a photo as an ImageView in a ScrollPane with an ZoomIn and ZoomOut Function. But if I reduce by means of scale the imageview, an undesirable empty edge is created in the ScrollPane. How can you make sure that the ScrollPane is always the size of the scaled ImageView?
See the following example. For simplicity, I replaced the ImageView with a rectangle.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollPaneDemo extends Application {
double scale;
Pane contPane = new Pane();
#Override
public void start(Stage primaryStage) {
BorderPane pane = new BorderPane();
ScrollPane sp = new ScrollPane();
sp.setContent(contPane);
sp.setVvalue(0.5);
sp.setHvalue(0.5);
Rectangle rec = new Rectangle(2820, 1240,Color.RED);
scale = 0.2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
contPane.getChildren().add(rec);
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
scale*=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
scale/=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
contPane scaled by using transform don't change its layoutBounds automatically. If you want not to make empty space in contPane, you'd better wrap the node in Group.
See this post. Layout using the transformed bounds
sp.setContent(new Group(contPane));
In addition, if you don't want to make empty space in ScrollPane, limit minimum scale to rate which width or height of the content fits viewport's one.
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
updateScale(scale * 2.0d);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
updateScale(scale / 2.0d);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
updateScale(0.2d);
private void updateScale(double newScale) {
scale = Math.max(newScale, Math.max(sp.getViewportBounds().getWidth() / rec.getWidth(), sp.getViewportBounds().getHeight() / rec.getHeight()));
contPane.setScaleX(scale);
contPane.setScaleY(scale);
}
Consider a case of the image is smaller than ScrollPane's viewport. Because for showing no empty space, this code will stretch contents when it doesn't have enough size.
In a case of huge images, TravisF's comment helps you.

JavaFx Multiple Layouts

I'm currently trying to create this Layout.
I've tried to use:
StackPane rootPane = new StackPane();
Scene scene = new Scene(rootPane,...);
Pane pane1 = new Pane();
Pane pane2 = new Pane();
rootPane.getChildren().addAll(pane1,pane2);
To let me create a menubar as well as a text field directly underneath it but it does not let me as the text field gets hidden by the menuBar.
I'm not sure which ones are needed in my case. I had a look at vbox - this is similar to what i what I need but I'm unsure how to add 2 tables in the last row with a gap
Would be a great help if you could point me in the direction needed.
StackPane is not a good choice here: it simply stacks child nodes on top of each other in z-order. I recommend you read the tutorial on layouts to get a full description of all the built-in layout panes, but one option is to use a VBox. To place the items in the bottom row, you could use an AnchorPane with one item anchored to the left and one to the right.
Here's an SSCCE using this approach:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class LayoutExample extends Application {
#Override
public void start(Stage primaryStage) {
VBox root = new VBox(5);
root.setPadding(new Insets(5));
MenuBar menuBar = new MenuBar();
menuBar.getMenus().add(new Menu("File"));
TextArea textArea = new TextArea();
VBox.setVgrow(textArea, Priority.ALWAYS);
AnchorPane bottomRow = new AnchorPane();
Label table1 = new Label("Table 1");
table1.setStyle("-fx-background-color: gray");
table1.setMinSize(200, 200);
Label table2 = new Label("Table 2");
table2.setStyle("-fx-background-color: gray");
table2.setMinSize(200, 200);
AnchorPane.setLeftAnchor(table1, 0.0);
AnchorPane.setRightAnchor(table2, 0.0);
bottomRow.getChildren().addAll(table1, table2);
root.getChildren().addAll(menuBar, textArea, bottomRow);
Scene scene = new Scene(root, 800, 800);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Another, similar, approach would be to use a BorderPane as the root, with the menu bar in the top, text area in the center, and anchor pane in the bottom.

keep elements X position fixed after scrolling javafx

How can I keep an element always visible even after scrolling in a scrollPane, that means the node should be immovable after a horizontal scrolling.its position should be fixed. I have tried this but it doesn't work for my case , the elements still moving with scrolling, I'm adding a scrollPane which contains all the elements to an AnchorPane.
Just use a stack pane and add the fixed elements after the ScrollPane.
Depending on what scrolling you do not want to allow just comment out the "scrollProperty" listener that I added - if you want the element completely fixed - remove them both:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
root.setAlignment(Pos.TOP_LEFT);
ScrollPane scrollPane = new ScrollPane();
Pane pane = new Pane();
pane.setMinHeight(1000);
pane.setMinWidth(1000);
scrollPane.setContent(pane);
root.getChildren().add(scrollPane);
Label fixed = new Label("Fixed");
root.getChildren().add(fixed);
// Allow vertical scrolling of fixed element:
scrollPane.hvalueProperty().addListener( (observable, oldValue, newValue) -> {
double xTranslate = newValue.doubleValue() * (scrollPane.getViewportBounds().getWidth() - fixed.getWidth());
fixed.translateXProperty().setValue(-xTranslate);
});
// Allow horizontal scrolling of fixed element:
scrollPane.vvalueProperty().addListener( (observable, oldValue, newValue) -> {
double yTranslate = newValue.doubleValue() * (scrollPane.getViewportBounds().getHeight() - fixed.getWidth());
fixed.translateYProperty().setValue(-yTranslate);
});
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
}

Resources