Strange behaviour when having a group in vbox - javafx

Can someone please explain me why this strange behaviour exists? When I have a group in a vbox every item in a child appears to modify the siblings to.
Following strange behaviour happens:
everything normal here
here too everything normal
whoops, why did the searchbar move???
First of the structure of the application I have:
root (VBox) //vbox so the menubar has its own space
├───menubar (MenuBar)
└───contentroot (Group)
├───searchbar (TextField) //searchbar should always be on the top left
└───nodeRoot (Group)
├───circle1 (Nodes)
└───circle2 (Nodes)
The root is a vbox so the menubar has its own unchallenged space. The searchbar should always be top left, directly under the menubar.
The nodeRoot should contain every other node. Kinda like a drawing board which I should be able to drag around.
Code:
public void start(Stage primaryStage) throws Exception{
VBox root = new VBox();
MenuBar menuBar = new MenuBar(new Menu("File"));
Group contentRoot = new Group();
contentRoot.getChildren().add(new TextField("SearchBar"));
Group nodeRoot = new Group();
contentRoot.getChildren().add(nodeRoot);
root.getChildren().addAll(menuBar, contentRoot);
Circle circle = new Circle(30, Color.RED);
nodeRoot.getChildren().add(circle);
Scene scene = new Scene(root, 300, 275);
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnMousePressed(event -> {
circle.setTranslateX(event.getSceneX() - 15);
circle.setTranslateY(event.getSceneY() - 15);
});
}
My guess why this happens:
The problem started to appear after I added the menubar and put everything into a VBox. This is when the siblings of the nodeRoot get changed too. My guess is that because VBox is a Region the behaviour is different than a normal group which expands. But then I dont understand why it only happens if the item moves to the left or top.
Can somebody please explain why this happens and how I can fix it?

From the javadocs for Group:
A Group will take on the collective bounds of its children and is not
directly resizable.
When you click near the top or left of the scene, the circle's bounds include negative values. Since the group takes on those bounds, it also takes on negative values. The TextField never has any layout bounds set, so the Group positions it at (0,0). Hence the text field can end up below or to the right of the circle. The vbox positions the group in order to try and contain it entirely, so it shifts it right if it contains negative x-value bounds and down if it contains negative y-value bounds.
If you use a Pane to contain the circle, instead of a Group:
Pane contentRoot = new Pane();
it behaves more intuitively: the Pane does not take on the union of the bounds of its child nodes, so if the circle has negative bounds, it just moves left and/or above the pane's visible area.

Related

JavaFX - Get Coordinates of Node Relative to its Parent

I am making a simple graphical interface for saving previously generated images. All images come to me square but I want to allow for some cropping functionality (more precisely cutting off equal parts from the bottom and top of the image). I want to do this by allowing the user to drag a shaded region over the image which will tell the user that this region will be cropped out. See the below image for details. To enable this drag functionality I have added small triangles that I want the user to drag which in turn will move the shaded regions about. However the coordinates for the triangles are all weird and seem nonsensical. Therefor I was wondering what the best way is to get the coordinates of the triangles in relation to the ImageView (or their first common parent node) in terms of ImageView-side-lengths. So if the triangle is in the center its coordinates are [0.5, 0.5] for instance.
The Image view will be moving around inside the scene and will also be changing size so it is vital that I can get the coordinates relative to not only the ImageView but also to the size of the ImageView.
Here is also the surrounding hierarchy of nodes if that helps. The Polygons are the triangles and the regions are the rectangles.
Thanks for all forms of help!
Node.getBoundsInParent returns the bounds of a node in it's parent coordinates. E.g. polygon.getBoundsInParent() would return the bounds in the VBox.
If you need to "go up" one additional step, you can use parent.localToParent to do this. vBox.localToParent(boundsInVbox) returns the bounds in the coordinate system of the AnchorPane.
To get values relative to the size of the image, you simply need to divide by it's size.
The following example only allows you to move the cover regions to in one direction and does not check, if the regions intersect, but it should be sufficient to demonstrate the approach.
The interesting part is the event handler of the button. It restricts the viewport of the second image to the part of the first image that isn't covered.
private static void setSideAnchors(Node node) {
AnchorPane.setLeftAnchor(node, 0d);
AnchorPane.setRightAnchor(node, 0d);
}
#Override
public void start(Stage primaryStage) {
// create covering area
Region topRegion = new Region();
topRegion.setStyle("-fx-background-color: white;");
Polygon topArrow = new Polygon(0, 0, 20, 0, 10, 20);
topArrow.setFill(Color.WHITE);
VBox top = new VBox(topRegion, topArrow);
top.setAlignment(Pos.TOP_CENTER);
topArrow.setOnMouseClicked(evt -> {
topRegion.setPrefHeight(topRegion.getPrefHeight() + 10);
});
// create bottom covering area
Region bottomRegion = new Region();
bottomRegion.setStyle("-fx-background-color: white;");
Polygon bottomArrow = new Polygon(0, 20, 20, 20, 10, 0);
bottomArrow.setFill(Color.WHITE);
VBox bottom = new VBox(bottomArrow, bottomRegion);
bottom.setAlignment(Pos.BOTTOM_CENTER);
bottomArrow.setOnMouseClicked(evt -> {
bottomRegion.setPrefHeight(bottomRegion.getPrefHeight() + 10);
});
Image image = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/402px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg");
ImageView imageView = new ImageView(image);
setSideAnchors(top);
setSideAnchors(bottom);
setSideAnchors(imageView);
AnchorPane.setTopAnchor(top, 0d);
AnchorPane.setBottomAnchor(bottom, 0d);
AnchorPane.setTopAnchor(imageView, 0d);
AnchorPane.setBottomAnchor(imageView, 0d);
AnchorPane container = new AnchorPane(imageView, top, bottom);
ImageView imageViewRestricted = new ImageView(image);
Button button = new Button("restrict");
button.setOnAction(evt -> {
// determine bouns of Regions in AnchorPane
Bounds topBounds = top.localToParent(topRegion.getBoundsInParent());
Bounds bottomBounds = bottom.localToParent(bottomRegion.getBoundsInParent());
// set viewport accordingly
imageViewRestricted.setViewport(new Rectangle2D(
0,
topBounds.getMaxY(),
image.getWidth(),
bottomBounds.getMinY() - topBounds.getMaxY()));
});
HBox root = new HBox(container, button, imageViewRestricted);
root.setFillHeight(false);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

.setLayoutX() not affecting position in FlowPane

For some reason or another textExampleTwo.setLayoutX(40) does not actually result in the Text moving at all to the right. Is this a bug or have I missed something important here?
public void start(Stage stage) throws Exception {
FlowPane flowPane = new FlowPane();
flowPane.setOrientation(Orientation.VERTICAL);
Text textExampleOne = new Text("An example - 1");
Text textExampleTwo = new Text("An example - 2");
textExampleTwo.setLayoutX(40.0);
flowPane.getChildren().addAll(textExampleOne, textExampleTwo);
Scene applicationScene = new Scene(flowPane);
stage.setHeight(400.0);
stage.setWidth(400.0);
stage.setScene(applicationScene);
stage.show();
}
You've missed something important here:
Many Panes including FlowPane determine the position of their children on their own. For positioning the layoutX and layoutY properties are used. If you assign a new value to one of them and the Node is a child of a layout that positions it's children itself, this just leads to the position to be changed back during the next layout pass.
The exception to this are Nodes with the managed property set to false. This leads to neither layoutX nor layoutY being assigned however.
In your case you seem to want a combination of the two.
In this case the desired effect can be achieved by setting a margin.
// set all insets except the left one to 0
FlowPane.setMargin(textExampleOne, new Insets(0, 0, 0, 40));
Note however that this does not set the x position to 40, but it keeps a space of size 40 at the left of the Node. If you add enough children before this node to move it to the second column, this spacing would be used to calculate the distance to the beginning of the column.

Resizeable Gridpane or other container

Hi I'm trying to create a simple layout that looks like this using JavaFX.
I would like the user to be able to drag/resize the middle bar. I've tried to make this using a GridPane. But I can't figure out how to get the middle resized. Perhaps GridPane is not the way to go. Both panes will contain a lot of other program code later on. Thanks!
Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
stageRef.setMaxWidth(primaryScreenBounds.getWidth());
stageRef.setMaxHeight(primaryScreenBounds.getHeight());
GridPane gridPane = new GridPane();
gridPane.setGridLinesVisible(true);
gridPane.setPadding(new Insets(10));
Scene scene = new Scene(gridPane);
VBox vBoxMain = new VBox();
vBoxMain.setPrefWidth((primaryScreenBounds.getWidth()/5)*4);
vBoxMain.setPrefHeight(primaryScreenBounds.getHeight());
TextArea textArea = new TextArea();
textArea.setWrapText(true);
textArea.setPrefHeight(primaryScreenBounds.getHeight());
vBoxMain.getChildren().addAll(textArea);
vBoxMain.isResizable();
VBox vBoxSide = new VBox();
vBoxSide.setPrefWidth((primaryScreenBounds.getWidth()/5));
vBoxSide.setPrefHeight(primaryScreenBounds.getHeight());
vBoxSide.isResizable();
gridPane.add(vBoxSide, 1,1,1,1);
gridPane.add(vBoxMain, 2,1,4,1);
stageRef.setScene(scene);
stageRef.show();
You could use a SplitPane:
A control that has two or more sides, each separated by a divider,
which can be dragged by the user to give more space to one of the
sides, resulting in the other side shrinking by an equal amount.
Then you add two others containers to this pane allowing the user to change the position of the divider. You can also set minimum widths for each component within the pane or set the position of each divider within your code.

JavaFX accessing stackpane within borderpane

The center of my BorderPane has a stackpane called designView (an FXML defined stackpane). I'm trying to get a draggable pane in the designView. If I add that pane to the rootView (my BordePane) all is well. If however I try to add it to the designView like this:
// This works fine except the pnae is on the wrong place obviously
....
Pane p = displayField.createDraggablePane(800.0, 800.0, 400.0, 300.0);
rootView.getChildren().add(p);
// Now the pane is in the right place, but it's 'stuck'
....
rootView.setCenter(designView);
Pane p = displayField.createDraggablePane(800.0, 800.0, 400.0, 300.0);
designView.getChildren().add(p);
The pane appears correctly in the designView, BUT it is no longer draggable. The MouseEvents fire, but the position of the pane is not updated. I think the problem is with the fact that layoutX, getSceneX, layoutXProperty etc. have no reference to designView, but how do I get that? Can anyone help?
I found a solution. Instead of adding a StackPane to my BorderPane center, I have added a Pane and all is well :-) So my draggable Pane is added to a Pane instead of added to a StackPane.
Don't quite understand why this should change the behaviour of the draggable pane, but practice shows that it does.

JavaFX: Menu items only shown as three dots when in BorderPane

I am trying to learn JavaFX and I've run into a problem with the Menus in my MenuBar. Here is a minimal example:
public void start(Stage mainStage) throws Exception {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 1200, 1000, Color.WHITE);
MenuBar menuBar = new MenuBar();
Menu menuFile = new Menu("_File");
menuBar.getMenus().add(menuFile);
MenuItem add = new MenuItem("_New");
menuFile.getItems().add(add);
root.getChildren().add(menuBar);
menuBar.prefWidthProperty().bind(mainStage.widthProperty());
mainStage.setScene(scene);
mainStage.show();
}
This application starts, but the Menu in the MenuBar is only shown as three dots (...). It does open however when I press ALT+F, so it is there.
From what I understand, a Menu item has no width or similar attribute, so that can't be set. I suspect it has something to do with my root node being a BorderPane, because in every other example I found that works, the root is either a VBox or something else. I seem to get the desired results when I place a Vbox as my root node, and then add the MenuBar and the BorderPane` to the root - but that seems like a strange and uneccesary workaround to me.
So what am I missing here? Is it true that a MenuBar will only ever look like it should in certain containers? What is the difference between a BorderPane and a VBox in this regard? If someone could explain or point me to a part of the documentation that I've missed, I would be very thankful.
You are using a BorderPane and using getChildren().add() to add MenuBar in it, which is incorrect. BorderPane unlike VBox, can't accept any number of children and is divided into 5 specific positions :
top
left
right
bottom
center
The children goes into any one of these positions. Please go through the documentation for BorderPane.
You need to add the Menubar to the top of the BorderPane using :
root.setTop(menuBar);

Resources