JavaFX: Determine Bounds of a node while being invisible? - javafx

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);
}
}

Related

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

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);
}
}

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.

How to get the size of a label before it is laid out?

I read this old answer on how to accomplish this. But since it involves using impl_processCSS(boolean), a method that is now deprecated, I think we need to update the answer.
I've tried placing the label inside a HBox and then get the size of it, or getting the size of the HBox, without any luck. And I've also tried using .label.getBoundsInLocal().getWidth().
SSCCE:
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.application.Application;
import javafx.scene.Scene;
public class SSCCE extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Label label = new Label("foo");
System.out.println(label.getWidth());
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Since JavaFX 8, the method you are looking for is applyCss().
As JavaDoc states:
apply styles to this Node and its children, if any. This method does not normally need to be invoked directly but may be used in conjunction with Parent.layout() to size a Node before the next pulse, or if the Scene is not in a Stage.
So you need to have the node in the container, and this one already on the scene, and also call layout().
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Label label = new Label("foo bla bla");
root.getChildren().add(label);
Scene scene = new Scene(root);
root.applyCss();
root.layout();
System.out.println(label.getWidth());
primaryStage.setScene(scene);
primaryStage.show();
}
Being the output: 17.818359375
Note I've changed HBox for Group, since:
a Group will "auto-size" its managed resizable children to their preferred sizes during the layout pass to ensure that Regions and Controls are sized properly as their state changes
If you use an HBox, you need to set also the dimensions of the scene:
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Label label = new Label("foo");
root.getChildren().add(label);
Scene scene = new Scene(root,100,20);
root.applyCss();
root.layout();
System.out.println(label.getWidth());
primaryStage.setScene(scene);
primaryStage.show();
}
Now the output is: 18.0

JavaFX Fill empty space when component is not visible?

i use Linux Suse 12.3, JDK 1.7.0-45, JavaFX 2.2.
my Question is: why the following Code not working and how to implement a toggleShow/hide functionality?
here is my Test Code:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.HTMLEditor;
import javafx.stage.Stage;
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(final Stage stage) {
AnchorPane root = new AnchorPane();
BorderPane inner = new BorderPane();
AnchorPane.setTopAnchor(inner, 0.0);
AnchorPane.setRightAnchor(inner, 0.0);
AnchorPane.setBottomAnchor(inner, 0.0);
AnchorPane.setLeftAnchor(inner, 0.0);
final HTMLEditor center = new HTMLEditor();
final ToolBar top = new ToolBar();
final Button button = new Button("hide");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
top.setVisible(false);
//center.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
}
});
center.setManaged(false);
top.getItems().add(button);
//top.managedProperty().bind(top.visibleProperty());
top.setManaged(false);
inner.setTop(top);
inner.setCenter(center);
root.getChildren().add(inner);
Scene scene = new Scene(root,600,400);
stage.setScene(scene);
stage.show();
}
}
what i want is the same effect as the Solution of Sergey to this Question but without changing width/height!:
How to solve the overlapping of the controls each other belonging to two different panes
as i said its just a Test Code. i tried using another Layouts as BorderPane but still not working. i don't want to recalculate the size's manually ...etc. Removing the node and adding it again is not an option for me.
whats wrong in my Code? any idea is welcomed!
thanks
Filling the empty space with the usage of BorderPane seems to be not an option, due to the prompt in its javadoc:
BorderPane lays out each child set in the five positions regardless of
the child's visible property value; unmanaged children are ignored.
Additionally, using AnchorPane just for resizable content as:
AnchorPane.setTopAnchor(inner, 0.0);
AnchorPane.setRightAnchor(inner, 0.0);
AnchorPane.setBottomAnchor(inner, 0.0);
AnchorPane.setLeftAnchor(inner, 0.0);
seems to be an overusing. Just using the VBox will be more suitable for your layout case.
Rewritten test code:
#Override
public void start(final Stage stage) {
final HTMLEditor center = new HTMLEditor();
final ToolBar top = new ToolBar();
final Button button = new Button("hide");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
top.setVisible(false);
top.setManaged(false);
}
});
top.getItems().add(button);
VBox inner = new VBox();
inner.getChildren().addAll(top, center);
Scene scene = new Scene(inner, 600, 400);
stage.setScene(scene);
stage.show();
}
Now the question is;
How are you going to implement the "show" part of your "show/hide" toggle bar? Since there is no clue about it in your question.
Try to remove the top.setManaged(false); line and then put the toolbar into a Group object like this:
Group g = new Group();
Region spacer = new Region();
spacer.setPrefWidth(10000);
spacer.setMinWidth(100);
top.getItems().add(spacer);
g.getChildren().add(top); //Toolbar here...
inner.setTop(g); //set Group into Borderpane instead of the toolbar
After clicking the button the free space is consumed entirely by the HTML-Editor.

Resources