Java FX - Reset translateX and translateY of node when it reaches 0 - javafx

I have 2 draggable StackPanes and between these is a line shape. When I drag one stack pane the end of the line next to the moving StackPane moves accordingly to the moving stack pane and the other side of the line stays still (which is what I want). However my problem is when I release the mouse i.e stop dragging the stack pane, the line goes back to its original position.
My event handler when you press the StackPane:
EventHandler<MouseEvent> circleOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane = ((StackPane)(t.getSource()));
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
layoutX = currentStackPane.getLayoutX();
layoutY = currentStackPane.getLayoutY();
}
};
My event handler when i drag the StackPane:
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
currentStackPane.setTranslateX(offsetX);
currentStackPane.setTranslateY(offsetY);
}
};
I tried make a event handler after the drag is finished:
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane.setLayoutX(layoutX + ((StackPane)(t.getSource())).getTranslateX());
currentStackPane.setLayoutY(layoutY + ((StackPane)(t.getSource())).getTranslateY());
currentStackPane.setTranslateX(0);
currentStackPane.setTranslateY(0);
}
};
Binding the start and end points to the stackpanes:
DoubleProperty startX = new SimpleDoubleProperty(vertexClickedOn.getLayoutX() + (vertexClickedOn.getWidth() / 2));
DoubleProperty startY = new SimpleDoubleProperty(vertexClickedOn.getLayoutY() + (vertexClickedOn.getHeight() / 2));
DoubleProperty endX = new SimpleDoubleProperty(vertexTo.getLayoutX() + (vertexTo.getWidth() / 2));
DoubleProperty endY = new SimpleDoubleProperty(vertexTo.getLayoutY() + (vertexTo.getHeight() / 2));
line.startXProperty().bind(startX.add(vertexClickedOn.translateXProperty()));
line.startYProperty().bind(startY.add(vertexClickedOn.translateYProperty()));
line.endXProperty().bind(endX.add(vertexTo.translateXProperty()));
line.endYProperty().bind(endY.add(vertexTo.translateYProperty()));
However if i take this out then the line stays where the mouse is released but the dragged stackpane goes back to its original position when the mouse released. If i keep this in then the stackpane stays where the mouse is released but the line goes back to it's original position.
How do I solve this?
Thank you.

The root cause for your problem is the startX/Y and endX/Y values are not updated with the new layoutX/Y values. Rather than taking into a separate variable, I would recommend to include them in the binding.
line.startXProperty().bind(vertexClickedOn.layoutXProperty().add(vertexClickedOn.translateXProperty()).add(vertexClickedOn.widthProperty().divide(2)));
line.startYProperty().bind(vertexClickedOn.layoutYProperty().add(vertexClickedOn.translateYProperty()).add(vertexClickedOn.heightProperty().divide(2)));
line.endXProperty().bind(vertexTo.layoutXProperty().add(vertexTo.translateXProperty()).add(vertexTo.widthProperty().divide(2)));
line.endYProperty().bind(vertexTo.layoutYProperty().add(vertexTo.translateYProperty()).add(vertexTo.heightProperty().divide(2)));

Related

JavaFx bindBidirectional Circle with textField

I try bind circle w textfield. When I try bind circle start jumping when I try move it. Why circle jump? When circle is unbind doesn't jump and I can move it unproblematically. Please help me. Where is the problem? I add text because stack overflow block my post
public class BindTest extends Application {
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setPadding(new Insets(5));
root.setHgap(10);
root.setVgap(10);
Scene scene = new Scene(root, 500, 250);
primaryStage.setTitle("Laczenie");
primaryStage.setScene(scene);
primaryStage.show();
TextField textFieldX = new TextField();
TextField textFieldY = new TextField();
Circle circle = new Circle(12, 22, 22, Color.YELLOW);
circle.setOnMousePressed(circleOnMousePressedHandler);
circle.setOnMouseDragged(circleOnMouseDraggedHandler);
root.add(textFieldX, 1, 0);
root.add(textFieldY, 2, 0);
root.add(circle, 2, 2);
circle.translateYProperty().addListener((observable, oldValue, newValue) -> {
textFieldX.setText((String.valueOf(circle.getCenterX() + newValue.doubleValue())));
});
circle.translateYProperty().addListener((observable, oldValue, newValue) -> {
textFieldY.setText((String.valueOf(circle.getCenterX() + newValue.doubleValue())));
});
StringConverter<Number> stringConverter = new NumberStringConverter();
textFieldX.textProperty().bindBidirectional(circle.translateXProperty(), stringConverter);
textFieldY.textProperty().bindBidirectional(circle.translateXProperty(), stringConverter);
}
public static void main(String[] args) {
launch(args);
}
}
private double coordinateX, coordinateY, orgTranslateX, orgTranslateY;
EventHandler<MouseEvent> circleOnMousePressedHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
coordinateX = event.getSceneX();
coordinateY = event.getSceneY();
orgTranslateX = ((Circle) (event.getSource())).getTranslateX();
orgTranslateY = ((Circle) (event.getSource())).getTranslateY();
}
};
EventHandler<MouseEvent> circleOnMouseDraggedHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double offsetX = event.getSceneX() - coordinateX;
double offsetY = event.getSceneY() - coordinateY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
((Circle) (event.getSource())).setTranslateX(newTranslateX);
((Circle) (event.getSource())).setTranslateY(newTranslateY);
}
};
You have two problems. One is a typo, you have textFieldY bound to circle.translateXProperty(), which probably causes all manner of weirdness.
More significantly, since you've bound the TextField text properties to the circle translate X & Y properties bidirectionally, you don't need the listeners. So what's happening is that you drag the circle which triggers the listeners which triggers the bindings which move the circle which triggers the listeners which triggers the bindings...
Just delete the listeners, fix the typo and everything works.

Why does my image leave the screen if the X/Y is set to anything but 0?

I am trying to make the bouncing DVD logo as a means of learning to use timeline and keyframe in javaFX. The problem I am running in to is if I set the X/Y of the image to anything other than 0,0 the image will go further than the bounds of the screen. I am just confused on why this is happening and what I need to do to fix it. Thank you!
I have tried setting the image to different areas on the pane. I have tried subtracting more than just the dvd width and height to compensate. I have tried many things.
public class Main extends Application {
Stage window;
private final int WIDTH = 700;
private final int HEIGHT = 700;
private Timeline timeline;
private double xSpeed = 3;
private double ySpeed = 3;
private Parent createContent() {
Pane root = new Pane();
root.setPrefSize(WIDTH,HEIGHT);
ImageView dvd = new ImageView(new Image("/dvd.png"));
dvd.setFitHeight(100);
dvd.setFitWidth(100);
dvd.setPreserveRatio(true);
dvd.setX(100);
dvd.setY(100);
dvd.setPreserveRatio(true);
timeline = new Timeline(new KeyFrame(Duration.millis(16), e-> {
dvd.setTranslateX(dvd.getTranslateX() + xSpeed);
dvd.setTranslateY(dvd.getTranslateY() + ySpeed);
if (xSpeed + dvd.getTranslateX() >= WIDTH - dvd.getFitWidth()){
xSpeed = -xSpeed;
} else if(xSpeed + dvd.getTranslateX() <= 0)
xSpeed = -xSpeed;
if (ySpeed + dvd.getTranslateY() >= HEIGHT - dvd.getFitHeight()){
ySpeed = -ySpeed;
} else if(ySpeed + dvd.getTranslateY() <= 0)
ySpeed = -ySpeed;
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
root.getChildren().add(dvd);
return root;
}
#Override
public void start(Stage primaryStage) throws Exception{
window = primaryStage;
Scene mainScene = new Scene(createContent(),WIDTH,HEIGHT);
window.setResizable(false);
window.setTitle("Bouncing DVD");
window.setScene(mainScene);
window.show();
}
public static void main(String[] args) {
launch(args);
}
}
I expect to be able to place the DVD image anywhere on the screen and for it to bounce off of the walls of the scene.
The x and y properties of ImageView are ways of moving the ImageView from it's usual position without affecting the translate properties. Any changes of the rendering position by transforms such as the translate properties happen in addition to this change.
The x and y ranges where the image is rendered are [x+translateX, x+translateX+fitWidth) and[y+translateY, y+translateY+fitHeight) respectively.
The simplest way of fixing this issue is using only a single property per dimension, e.g. translateX and translateY:
dvd.setFitHeight(100);
dvd.setFitWidth(100);
dvd.setPreserveRatio(true);
dvd.setTranslateX(100);
dvd.setTranslateY(100);

Fitting rotated ImageView into Application Window / Scene

In JavaFX I am trying to show an rotated ImageView in an Application Window.
Therefore I have put it into a stackPane to have it always centered and I have bound the widths/heights of the ImageView and the stackPane to the scene's width/height to view it just as large as possible.
This works fine as soon as the Image is not rotated.
As soon as I rotate the Image by 90° using stackPane.setRotate(90) (and exchange binding for width/height) then the stackPane is no longer bound to the upper left corner of the Application Window (or scene).
What can I do to place the rotated image correctly?
In the example code [any key] will toggle the rotation 90°/0° so the location problem of the rotated image becomes visible:
public class RotationTest extends Application {
boolean rotated = false;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Rotation test");
Group root = new Group();
Scene scene = new Scene(root, 1024,768);
//a stackPane is used to center the image
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-background-color: black;");
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
//toggle rotate 90° / no rotation
rotated = !rotated;
stackPane.prefHeightProperty().unbind();
stackPane.prefWidthProperty().unbind();
if (rotated){
stackPane.setRotate(90);
//rotation: exchange width and height for binding to scene
stackPane.prefWidthProperty().bind(scene.heightProperty());
stackPane.prefHeightProperty().bind(scene.widthProperty());
}else{
stackPane.setRotate(0);
//no rotation: height is height and width is width
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
}
}
});
final ImageView imageView = new ImageView("file:D:/test.jpg");
imageView.setPreserveRatio(true);
imageView.fitWidthProperty().bind(stackPane.prefWidthProperty());
imageView.fitHeightProperty().bind(stackPane.prefHeightProperty());
stackPane.getChildren().add(imageView);
root.getChildren().add(stackPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Results:
Without rotation the stackPane (black) fits the window perfectly and the image has the correct size even if the window is resized with the mouse.
After pressing [any key] the stackPane is rotated. The stackPane (black) seems to have the correct width/height and also the image seems to be correctly rotated. But the stackPane is no longer in the upper left corner??? It moves around when the window is resized with the mouse???
Why not simply leave the Group and the preferred sizes out of the equation?
The root is automatically resized to fit the scene and you can use it's width/height properties to bind the fitWidth and fitHeight properties:
private static void setRotated(boolean rotated, ImageView targetNode, Pane parent) {
double angle;
if (rotated) {
angle = 90;
targetNode.fitWidthProperty().bind(parent.heightProperty());
targetNode.fitHeightProperty().bind(parent.widthProperty());
} else {
angle = 0;
targetNode.fitWidthProperty().bind(parent.widthProperty());
targetNode.fitHeightProperty().bind(parent.heightProperty());
}
targetNode.setRotate(angle);
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
StackPane root = new StackPane(imageView);
root.setStyle("-fx-background-color: black;");
// initialize unrotated
setRotated(false, imageView, root);
Scene scene = new Scene(root, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
setRotated(imageView.getRotate() == 0, imageView, root);
});
primaryStage.setScene(scene);
primaryStage.show();
}
Note that this may not result in correct layout, if placed in some other layout, since the size constraints may be calculated wrong.
You could implement your own region though to fix this:
public class CenteredImage extends Region {
private final BooleanProperty rotated = new SimpleBooleanProperty();
private final ImageView imageView = new ImageView();
public CenteredImage() {
// make sure layout gets invalidated when the image changes
InvalidationListener listener = o -> requestLayout();
imageProperty().addListener(listener);
rotated.addListener((o, oldValue, newValue) -> {
imageView.setRotate(newValue ? 90 : 0);
requestLayout();
});
getChildren().add(imageView);
imageView.setPreserveRatio(true);
}
public final BooleanProperty rotatedProperty() {
return rotated;
}
public final void setRotated(boolean value) {
this.rotated.set(value);
}
public boolean isRotated() {
return rotated.get();
}
public final void setImage(Image value) {
imageView.setImage(value);
}
public final Image getImage() {
return imageView.getImage();
}
public final ObjectProperty<Image> imageProperty() {
return imageView.imageProperty();
}
#Override
protected double computeMinWidth(double height) {
return 0;
}
#Override
protected double computeMinHeight(double width) {
return 0;
}
#Override
protected double computePrefWidth(double height) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && height > 0) {
height -= insets.getBottom() + insets.getTop();
add = isRotated()
? height / image.getWidth() * image.getHeight()
: height / image.getHeight() * image.getWidth();
}
return insets.getLeft() + insets.getRight() + add;
}
#Override
protected double computePrefHeight(double width) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && width > 0) {
width -= insets.getLeft() + insets.getRight();
add = isRotated()
? width / image.getHeight() * image.getWidth()
: width / image.getWidth() * image.getHeight();
}
return insets.getTop() + insets.getBottom() + add;
}
#Override
protected double computeMaxWidth(double height) {
return Double.MAX_VALUE;
}
#Override
protected double computeMaxHeight(double width) {
return Double.MAX_VALUE;
}
#Override
protected void layoutChildren() {
Insets insets = getInsets();
double left = insets.getLeft();
double top = insets.getTop();
double availableWidth = getWidth() - left - insets.getRight();
double availableHeight = getHeight() - top - insets.getBottom();
// set fit sizes
if (isRotated()) {
imageView.setFitWidth(availableHeight);
imageView.setFitHeight(availableWidth);
} else {
imageView.setFitWidth(availableWidth);
imageView.setFitHeight(availableHeight);
}
// place image
layoutInArea(imageView, left, top, availableWidth, availableHeight, 0, null, false,
false, HPos.CENTER, VPos.CENTER);
}
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
CenteredImage imageArea = new CenteredImage();
imageArea.setImage(image);
imageArea.setStyle("-fx-background-color: black;");
imageArea.setPrefWidth(300);
SplitPane splitPane = new SplitPane(new Region(), imageArea);
SplitPane.setResizableWithParent(imageArea, true);
Scene scene = new Scene(splitPane, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
imageArea.setRotated(!imageArea.isRotated());
});
primaryStage.setScene(scene);
primaryStage.show();
}
I found a solution :-) Fabian's approach inspired me (thank you!!) And my old friend Pit helped me with debugging (also thank you!!)
It seems that the layout location algorithm of JavaFX has a problem when resize() is applied to rotated Panes (or even Nodes - I have not tried):
Following Fabian's idea I debugged into the layoutChildren() method of class Pane. I found that the relocation after setRotate() is correct and keeps the center of the child pane as expected. But as soon as resize() is called (which is done because of fitting the rotated child pane again into its father and additionally always when the window is resized by the user) the origin calculation goes wrong:
The picture above depicts a sequence of setRotate(90), resize() and relocate() in green and the same for setRotate(270) in blue. A little blue/green circle depicts the corresponding origin together with its coordinates in the 1024x786 example.
Analysis
It seems that for calculation the position of the Pane resize() does not use the height and width from BoundsInParent-Property (see JavaFX-Docu of Node) but from getWidth() and getHeight() which seem to reflect BoundsInLocal. As a consequence, for rotations of 90° or 270° height and width seem to be interchanged. Therefore the error in the calculation for the new origin is just the half of the difference between width and height (delta=(width-height)/2) when resize() tries to center the child pane again after the resizing.
Solution
A relocation(delta,-delta) needs to be applied after resizing for Panes with rotation=90 or 270 degrees.
The structure of my implementation follows Fabian's basic idea: I have build a layouter RotatablePaneLayouter:Region that just overwrites the layoutChildren() method. In its constructor it gets a Pane (in my example a StackPane) which can contain any number of children (in my example an ImageView) and that can be rotated.
LayoutChildren() then just executes resize() and relocate() for the child pane to fit it completely into the RotateablePaneLayouter respecting the orientation of the child pane.
The Layouter Helper (RotateablePaneLayouter:Region)
public class RotatablePaneLayouter extends Region {
private Pane child;
public RotatablePaneLayouter(Pane child) {
getChildren().add(child);
this.child = child;
// make sure layout gets invalidated when the child orientation changes
child.rotateProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
requestLayout();
}
});
}
#Override
protected void layoutChildren() {
// set fit sizes:
//resize child to fit into RotatablePane and correct movement caused by resizing if necessary
if ((child.getRotate() == 90)||(child.getRotate() == 270)) {
//vertical
child.resize( getHeight(), getWidth() ); //exchange width and height
// and relocate to correct movement caused by resizing
double delta = (getWidth() - getHeight()) / 2;
child.relocate(delta,-delta);
} else {
//horizontal
child.resize( getWidth(), getHeight() ); //keep width and height
//with 0° or 180° resize does no movement to be corrected
child.relocate(0,0);
}
}
}
To use it: Place the Pane to be rotated into the Layouter first instead of placing the Pane directly.
Here the code for the example's main program. You can use the space bar to rotate the child pane by 90, 180, 270 and again 0 degrees. You can also resize the window with the mouse. The layouter always manages to place the rotated pane correctly.
Expample for using the Layouter
public class RotationTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
//image in a StackPane to be rotated
final ImageView imageView = new ImageView("file:D:/Test_org.jpg");
imageView.setPreserveRatio(true);
StackPane stackPane = new StackPane(imageView); //a stackPane is used to center the image
stackPane.setStyle("-fx-background-color: black;");
imageView.fitWidthProperty().bind(stackPane.widthProperty());
imageView.fitHeightProperty().bind(stackPane.heightProperty());
//container for layouting rotated Panes
RotatablePaneLayouter root = new RotatablePaneLayouter(stackPane);
root.setStyle("-fx-background-color: blue;");
Scene scene = new Scene(root, 1024,768);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.SPACE) {
//rotate additionally 90°
stackPane.setRotate((stackPane.getRotate() + 90) % 360);
}
}
});
primaryStage.setTitle("Rotation test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
For me this seems like a workaround of a javaFX bug in resize().

After translating node The layout x and y stays the same

I currently have some stack panes on a pane and when dragged with a mouse they move around the pane. I do this by getting the coordinate of the mouse
and the translate x and y of the stack pane when I press the the stack pane. Then when I start to drag the stack pane I set the the translation x and y of the stack pane to the mouse coordinates when I pressed the stack pane + the difference of the new mouse coordinates and the old mouse coordinates.
My problem is after dragging the StackPane the layout x and y stays the same I want to update this as I used this else where.
My event handler when you press the StackPane:
EventHandler<MouseEvent> circleOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane = ((StackPane)(t.getSource()));
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
layoutX = currentStackPane.getLayoutX();
layoutY = currentStackPane.getLayoutY();
}
};
My event handler when i drag the StackPane:
EventHandler<MouseEvent> circleOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
currentStackPane.setTranslateX(offsetX);
currentStackPane.setTranslateY(offsetY);
}
};
I tried make a event handler after the drag is finished:
EventHandler<MouseEvent> circleOnMouseReleasedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
currentStackPane.setLayoutX(layoutX + ((StackPane)(t.getSource())).getTranslateX());
currentStackPane.setLayoutY(layoutY + ((StackPane)(t.getSource())).getTranslateY());
currentStackPane.setTranslateX(0);
currentStackPane.setTranslateY(0);
}
};
But this doesn't seem to work. Any help would be appreciated thanks!
EDIT:
I have changed my event handlers. It seems to be updating the layout x and y correctly for the first time I drag the stack pane but when i first drag the stack pane and then release the mouse the stackpane moves to a different position then every time i drag after it messes up completely. Not sure why, any help appreciated!
EDIT2: I realised I set translate x to 0 but didnt set translate y to 0 in the mouse released event. It all works now!
To understand the problem, I would first recommend to have a look at the documentation of layoutX/layoutY properties of a Node.
public final DoubleProperty layoutXProperty
Defines the x coordinate of the translation that is added to this
Node's transform for the purpose of layout. The value should be
computed as the offset required to adjust the position of the node
from its current layoutBounds minX position (which might not be 0) to
the desired location.
For example, if textnode should be positioned at finalX
textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
Failure to subtract layoutBounds minX may result in misplacement of
the node. The relocate(x, y) method will automatically do the correct
computation and should generally be used over setting layoutX
directly.
The node's final translation will be computed as layoutX + translateX,
where layoutX establishes the node's stable position and translateX
optionally makes dynamic adjustments to that position.
If the node is managed and has a Region as its parent, then the layout
region will set layoutX according to its own layout policy. If the
node is unmanaged or parented by a Group, then the application may set
layoutX directly to position it.
In short,for every node that is rendered in the scene, its position is actually a sum of its layoutX/Y and translateX/Y values in relative to its parent node. The layoutX/Y are initially updated as per its parents layout policy. For that reason, there is no point in updating/relying on layoutX/Y values of node, IF its parent (eg,.StackPane,HBox,VBox,..etc) manages it position.
Pane will not manage/decide its children layout. For that reason the default layoutX/Y values of its children is always 0.
From the above info,if we now look into your code, you are updating the translate values and setting the layout values wrongly. Instead what you have to actually do is:
Take intial values of layoutX/Y.
Update the translateX/Y while dragging.
And on mouse released recompute layoutX/Y values and reset
translateX/Y values.
Below is a quick demo of what I have described.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class PaneLayoutDemo extends Application {
double sceneX, sceneY, layoutX, layoutY;
#Override
public void start(Stage stage) throws Exception {
Pane root = new Pane();
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
root.getChildren().addAll(getBox("green"), getBox("red"), getBox("yellow"));
}
private StackPane getBox(String color) {
StackPane box = new StackPane();
box.getChildren().add(new Label("Drag me !!"));
box.setStyle("-fx-background-color:" + color + ";-fx-border-width:2px;-fx-border-color:black;");
box.setPrefSize(150, 150);
box.setMaxSize(150, 150);
box.setMinSize(150, 150);
box.setOnMousePressed(e -> {
sceneX = e.getSceneX();
sceneY = e.getSceneY();
layoutX = box.getLayoutX();
layoutY = box.getLayoutY();
System.out.println(color.toUpperCase() + " Box onStart :: layoutX ::" + layoutX + ", layoutY::" + layoutY);
});
box.setOnMouseDragged(e -> {
double offsetX = e.getSceneX() - sceneX;
double offsetY = e.getSceneY() - sceneY;
box.setTranslateX(offsetX);
box.setTranslateY(offsetY);
});
box.setOnMouseReleased(e -> {
// Updating the new layout positions
box.setLayoutX(layoutX + box.getTranslateX());
box.setLayoutY(layoutY + box.getTranslateY());
// Resetting the translate positions
box.setTranslateX(0);
box.setTranslateY(0);
});
return box;
}
public static void main(String[] args) {
Application.launch(args);
}
}
Once you are familiar with the demo, try changing the root from Pane to StackPane and see the behaviour difference.

JavaFX MouseEvent not firing after moving nodes in GridPane

I am attempting to create a "draggable" histogram UI with JavaFX. I have a ScrollPane containing a GridPane with 1 column and lots of rows. In each row is an HBox containing a label. Every 10 rows, there is also an HBox containing a Line.
I tried to make the HBoxes containing lines draggable by setting onMousePressed, onMouseDragged, and onMouseReleased event handlers (shown below). It works if I drag and release an hbox-line above its starting point - it ends up in whatever grid row I put it in, and I can click and drag it again. However, if I drag and release a line below its starting point, I can't get any more mouseEvents for that hBox. I tried adding log statements everywhere, nothing. I tried setting onMouseOver, it also was not fired.
Why would moving an hbox around the grid like this work for dragging up but not down?
lineContainer.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
EventTarget target = mouseEvent.getTarget();
lastY = mouseEvent.getSceneY();
}
});
lineContainer.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
Node target = (Node) mouseEvent.getTarget();
HBox hBox = null;
if (target instanceof HBox) {
hBox = (HBox) target;
}
else if (target instanceof Line) {
hBox = (HBox) target.getParent();
}
else { //should never happen
log.info("target not hbox or line: " + target.getClass());
}
if (mouseEvent.getSceneY() <= (lastY - 15)) {
int row = GridPane.getRowIndex(hBox);
GridPane.setRowIndex(hBox, --row);
lastY = mouseEvent.getSceneY();
lastRow = row - 1;
} else if (mouseEvent.getSceneY() >= (lastY + 15)) {
int row = GridPane.getRowIndex(hBox);
GridPane.setRowIndex(hBox, ++row);
lastRow = row - 1;
lastY = mouseEvent.getSceneY();
}
}
});
lineContainer.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
Node tar = (Node) mouseEvent.getTarget();
HBox hBox = null;
if (tar instanceof HBox) {
hBox = (HBox) tar;
}
else if (tar instanceof Line && tar.getParent() instanceof HBox) {
hBox = (HBox) tar.getParent();
}
else { //should never happen
log.info(mouseEvent.getTarget().getClass().toString());
}
}
});
UPDATE: I managed to get it working by creating a new HBox, resetting the onMouse... handlers, and copying its children every time the mouse is released. But I still don't know what was causing the original issue...
The following isn't a direct solution for you, but I wanted to say I have a similar problem and share my observations.
My application allows dragging in both axes (X, Y). All I've been able to figure out that some invisible element is 'obscuring' the MouseEvent hitbox. Testing it using an 'MS minesweeper' approach, shows this interfering area to extend from coords (0,0) of the root to (maxX,maxY) of another Node I have in the scene which is a layer above.
My problem was solved by changing z-order of Parent objects (let's call them layers) containing the Nodes that didn't receive their MouseEvents.
JavaFX MouseEvent doc explains that it is only the top level node which receives the event:
https://docs.oracle.com/javafx/2/api/javafx/scene/input/MouseEvent.html
Also look at the pickOnBounds property: JavaFX: How to make a Node partially mouse transparent?

Resources