How to make JavaFX Group not move when you just add children?

If you look at the next test, you will see that all circles are moving, not just the new one. This does not happen every time. I think this only happens when a new child is beyond existing boundaries. But how can I get it so that it does not move the group and all its children when I add another circle, no matter where I put the circle?

Please note that if I do not adjust the scale in the group, they will not move. Therefore, this is due to the installation of the scale.

import javafx.application.*; import javafx.beans.value.*; import javafx.collections.*; import javafx.scene.*; import javafx.scene.layout.*; import javafx.scene.shape.*; import javafx.stage.*; import java.util.*; public class GroupTest extends Application { public static void main(String[] args) { launch(args); } public void start(Stage stage) { Pane pane = new Pane(); Group root = new Group(); // NOTE: removing these two setScale* lines stops the undesirable behavior root.setScaleX(.2); root.setScaleY(.2); root.setTranslateX(100); root.setTranslateY(100); root.layoutXProperty().addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observableValue, Number number, Number number2) { System.out.println("root layout: " + root.getLayoutX() + ", " + root.getLayoutY()); } }); root.getChildren().addListener(new ListChangeListener<Node>() { @Override public void onChanged(Change<? extends Node> change) { System.out.println("root: " + root.getBoundsInParent()); System.out.println("root: " + root.getBoundsInLocal()); System.out.println("root: " + root.getLayoutBounds()); System.out.println("root: " + root.getLayoutX() + ", " + root.getLayoutY()); } }); pane.getChildren().add(root); Scene scene = new Scene(pane, 500, 500); stage.setScene(scene); stage.show(); new Thread(() -> { Random r = new Random(); try { while (true) { expand = expand * 1.1; Thread.sleep(700); Platform.runLater(() -> { root.getChildren().add(new Circle(r.nextInt((int)(1000*expand)) - 500*expand, r.nextInt((int)(1000*expand)) - 500*expand, r.nextInt(50)+30)); }); } } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } static double expand = 1.0; } 
+6
source share
5 answers

First, I want to say that the behavior that you see can be achieved with a much smaller program, not to mention the calculations you do for circles. r.nextInt(250) for the positions of the circles would be enough to see the behavior, and it is much easier to see what happens. In addition, for debugging, I added a visible rectangle to the panel attached to the lines of the group layout, where you can see what happens:

 final Rectangle background = new Rectangle(0, 0, 0, 0); root.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() { @Override public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) { background.setX(newValue.getMinX()); background.setY(newValue.getMinY()); background.setWidth(newValue.getWidth()); background.setHeight(newValue.getHeight()); } }); background.setFill(null); background.setStroke(Color.RED); pane.getChildren().add(background); 

So what is going on here?

From the Group API:

Any transformation, effect, or state applied to a group will apply to all children of this group. Such transformations and effects will NOT be included in the framework of this group, however, if translations and effects are set directly for children of this group, they will be included in the framework of this group.

Looking at the result with the included weight:

enter image description here

You see that the boundaries of the group are greater than those inside. This is due to how the transformation is applied: the children of the group are transformed, but scaling is not considered to calculate the boundaries of the group. Thus, the group is in the panel where the unformed transformed circle boundaries are merged, then the transforms for the circles are applied.

Compare this statement with the result when scaling is disabled:

enter image description here

To summarize, this is a design, not a strange behavior, because the Group is always as big and positioned accordingly, where the limitations of its untransformed children are combined.

EDIT

If you want the nodes to be scaled in the position they are in and the group is not moving, I suggest directly scaling the children of the group. This implementation of your thread changes the scale of the circles for every 5 circles, but they remain in the same position:

 new Thread(new Runnable() { private int count = 0; private double scale1 = .5; private double scale2 = .2; private double currentScale = scale1; @Override public void run() { final Random r = new Random(); try { while (true) { expand = expand * 1.1; Thread.sleep(700); Platform.runLater(new Runnable() { @Override public void run() { System.out.println(count); Circle c = new Circle(r.nextInt(250), r.nextInt(250), 30); c.setScaleX(currentScale); c.setScaleY(currentScale); root.getChildren().add(c); count++; if (count > 5){ count = 0; if (currentScale == scale1){ currentScale = scale2; } else { currentScale = scale1; } Iterator<Node> iterator = root.getChildren().iterator(); while (iterator.hasNext()) { Node next = iterator.next(); next.setScaleX(currentScale); next.setScaleY(currentScale); } } } }); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); 
+5
source

The real problem here is that the pivot point of the group β€” the relative point for rotation and scaling β€” depends on the boundaries of the group layout. Therefore, if changes to the layout change due to the addition of new children (or existing children who change position, size or rotation), the relative point for all transformations in the Group also changes.

In the JavaFX source code, you can find the definition of the pivot point in the Node.java file. The impl_getPivotX() and impl_getPivotY() methods return the center x, respectively. y coordinates of layouts.

Unfortunately, there is no way to manually set the reference point. But since each Node manages a list of additional transformations that are applied on top of standard transforms, you can easily achieve the desired behavior:

 final Scale scale = new Scale(); group.getTransforms().add(scale); // change scale scale.setX(2); 
+3
source

Because you change the size of the group after adding each circle, thereby causing the group to move inside the panel.

You can:

  • add circles directly to the panel

     pane.getChildren().add(new Circle(... 
  • add a large background to fix the group size:

     // numbers are huge because you are using 1/5 scaling Rectangle base = new Rectangle(-10000, -10000, 20000, 20000); base.setFill(Color.LIGHTGRAY); root.getChildren().add(base); 
+1
source

I had the same problem as when drawing a signal that has some values ​​that go beyond the bounds.

Use a panel instead of a group. You also use absolute positioning, and it does not move around its borders. JavaFx 8 Panel

+1
source

Use javafx.scene.layout.Pane instead of javafx.scene.Group.

-2
source

Source: https://habr.com/ru/post/948550/


All Articles