JavaFx ImageView doesn't trigger Mouse Events such as press or drag if you click or drag on a transparent pixel, is there anyway to work around this and detect mouse events from transparent areas ?
I have this image
that i added into this very simple JavaFX scene
using an ImageView named view and i want to move it with Mouse Drag events
so i wrote this code
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application{
double initMx, initMy,initX, initY;
#Override
public void start(Stage ps) throws Exception {
StackPane pane = new StackPane();
Image im = new Image("0.png");
ImageView view = new ImageView(im);
double fact = im.getWidth() / im.getHeight();
view.setFitHeight(300);
view.setFitWidth(300 * fact);
view.setOnMousePressed(e->{
initX = view.getTranslateX();
initY = view.getTranslateY();
initMx = e.getSceneX();
initMy = e.getSceneY();
});
view.setOnMouseDragged(e->{
double dx = initMx - e.getSceneX();
double dy = initMy - e.getSceneY();
double nx = initX - dx;
double ny = initY - dy;
view.setTranslateX(nx);
view.setTranslateY(ny);
});
pane.getChildren().add(view);
Scene scene = new Scene(pane, 500, 500);
ps.setScene(scene);
ps.show();
}
public static void main(String[] args) {
launch(args);
}
}
this code works fine so far,
but if you press or drag somewhere like under his ears ( or anywhere transparent ) nothing will happen ! how to fix this !
The more natural and easiest solution would have been to just set pick on bounds to true.
view.setPickOnBounds(true);
You can do so by setting this image as a graphic in a Button like so
button.setGraphics(new ImageView(im));
Note: You will need to remove style from the button after adding the ImageView by setting the button background with a transparent background color
Try this, if you didn't yet:
view.setOnMouseDragged(e->{
double dx = initMx - e.getX();
double dy = initMy - e.getY();
Related
The idea is to draw a ball on MOUSE_CLICKED
And draw a line starting from its center till it released in the MOUSE_DRAGGED handler.
but it does complete opposite and I do not simply get it - it draws first a line and after i release the mouse the ball appears.
Does anyone see where is the problem?
public class Step extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Canvas layer1 = new Canvas(500, 500);
Group root = new Group();
root.getChildren().add(layer1);
Ball c_ball = new Ball(0, 0, 50, 0, 0);
Arrow arrow = new Arrow(0, 0, 0, 0);
layer1.addEventHandler(MouseEvent.MOUSE_CLICKED,
ev -> {
c_ball.x = ev.getX();
c_ball.y = ev.getY();
arrow.start_x = ev.getX();
arrow.start_y = ev.getY();
GraphicsContext gc = layer1.getGraphicsContext2D();
gc.setFill(Color.DARKCYAN);
gc.fillOval(c_ball.x - c_ball.size / 2, c_ball.y - c_ball.size / 2, c_ball.size, c_ball.size);
});
layer1.addEventHandler(MouseEvent.MOUSE_DRAGGED,
ev -> {
GraphicsContext gc_arr = layer1.getGraphicsContext2D();
gc_arr.clearRect(0, 0, layer1.getWidth(), layer1.getHeight());
gc_arr.strokeLine(arrow.start_x, arrow.start_y, ev.getX(), ev.getY());
});
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.show();
}
}
Here is class Ball :
public class Ball {
public double x, y;
public double dx, dy;
public double size;
public Ball(double x, double y, double size, double dx, double dy) {
this.x = x;
this.y = y;
this.size = size;
this.dx = dx;
this.dy = dy;
}}
And arrow
public class Arrow {
public double start_x, start_y;
public double end_x, end_y;
public Arrow(double x1, double y1, double x2, double y2) {
this.start_x = x1;
this.start_y = y1;
this.end_x = x2;
this.end_y = y2;
}}
Looking at MouseEvent.MOUSE_CLICKED documentation you will see :
EventType javafx.scene.input.MouseEvent.MOUSE_CLICKED
This event occurs when mouse button has been clicked (pressed and
released on the same node). This event provides a button-like
behavior to any node. Note that even long drags can generate click
event (it is delivered to the top-most node on which the mouse was
both pressed and released).
The mouse event you are looking for is MouseEvent.MOUSE_PRESSED but in the case your code is not going to work neither. The reason is you clear everything inside the MOUSE_DRAGGED event, when you call gc_arr.clearRect(0, 0, layer1.getWidth(), layer1.getHeight());.Thus you will draw the circle but you will clear the canvas and only draw the line. If you remove that line you will have multiple lines each time you drag your mouse but that's a different problem.
You will need to make a big step back and think, do i really need to implement my program with Canvas?
If the answer is YES you will need to keep inside a list (or some data structure like array etc) all the components and when you want to change something you need to update the objects and redraw everything
If the answer is NO then you are taking the correct approach and you will need to change the canvas to an AnchorPane or Pane etc and just add the Nodes ( Circle , Lines etc ) directly on the pane.
Here is a simple example with AnchorPane instead of Canvas.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class ShapesTest extends Application {
public static void main(String[] args) {
launch(args);
}
private double arrayStartX;
private double arrayStartY;
private AnchorPane root;
private Line l;
#Override
public void start(Stage stage) {
root = new AnchorPane();
root.addEventHandler(MouseEvent.MOUSE_PRESSED, ev -> {
addBall(ev.getX(), ev.getY());
arrayStartX = ev.getX();
arrayStartY = ev.getY();
});
root.addEventHandler(MouseEvent.MOUSE_DRAGGED, ev -> {
if (l == null) {
addLine(ev.getX(), ev.getY());
} else {
l.setEndX(ev.getX());
l.setEndY(ev.getY());
}
});
root.addEventHandler(MouseEvent.MOUSE_RELEASED, ev -> {
l = null;
});
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.show();
}
private void addLine(double x, double y) {
l = new Line(arrayStartX, arrayStartY, x, y);
root.getChildren().add(l);
}
private void addBall(double x, double y) {
Circle c = new Circle(x, y, 15);
c.fillProperty().set(Color.DARKCYAN);
root.getChildren().add(c);
}
}
In the example above you could keeps the Circle and the Lines informations on your own custom object if you like and then apply physics on each ball when the mouse is released.
I have this code where I have random rectangles generating over the entire canvas. I also have 1 rectangle at the center.
I need to get the 1 rectangle at the center to move around when the user hovers the mouse over the canvas.
Here is my code
package sample;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
private static final int DRAW_WIDTH = 800;
private static final int DRAW_HEIGHT = 500;
private Animation myAnimation;
private Canvas canvas;
private GraphicsContext gtx;
#Override
public void start(Stage stage) throws Exception
{
stage.setTitle("tRIPPy BoXXX");
canvas = new Canvas(DRAW_WIDTH, DRAW_HEIGHT);
gtx = canvas.getGraphicsContext2D();
gtx.setLineWidth(3);
gtx.setFill(Color.BLACK);
gtx.fillRect(0, 0, DRAW_WIDTH, DRAW_HEIGHT);
VBox vBox = new VBox();
vBox.getChildren().addAll(canvas);
Scene scene = new Scene(vBox, DRAW_WIDTH, DRAW_HEIGHT);
stage.setScene(scene);
stage.show();
myAnimation = new Animation();
myAnimation.start();
}
class Animation extends AnimationTimer
{
#Override
public void handle(long now)
{
Random rand = new Random();
double red = rand.nextDouble();
double green = rand.nextDouble();
double blue = rand.nextDouble();
Color boxColor = Color.color(red,green,blue);
gtx.setStroke(boxColor);
....
This is the box that I want to have moving around with the users mouse. I have tried some things on my own, however I can't get the rect to stay the way it is in the code.
.....
int rectX = 800 / 2;
int rectY = 500 / 2;
for (int side = 10; side <= 100; side += 10) {
gtx.strokeRect(rectX - side / 2, rectY - side / 2, side, side);
}
int centerX = (int)(Math.random()*800);
int centerY = (int)(Math.random()*800);
for (int side = (int)(Math.random()*100); side <= Math.random()* 100; side += Math.random()*100)
{
gtx.strokeRect(centerX - side / 2, centerY - side / 2, side, side);
}
}
}
public static void main(String[] args)
{
launch(args);
}
If you plan to move some graphics arround, why do you then start with a canvas? On a canvas you cannot move anything arround unless you constantly want to redraw everything again and again. Putting your rectangles into the scene graph is much better suited for this task.
I am trying to create a very simple, easy-to-use program that actively reads and displays the position of the mouse. I have seen many tutorials that create programs that read the position of the mouse only when it is inside the window of the GUI application, or after hitting a button, but I want one that displays the position of the mouse in all areas of the screen. This is what I have:
import java.awt.MouseInfo;
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.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class MouseCoordinates extends Application{
public static void main(String[] args) {
launch();
}
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Mouse Reader");
Label x = new Label();
Label y = new Label();
StackPane layout = new StackPane();
layout.getChildren().addAll(x, y);
Scene scene = new Scene(layout, 600, 500);
primaryStage.setScene(scene);
primaryStage.show ();
double mouseX = 1.0;
double mouseY = 1.0;
while(true){
mouseX = MouseInfo.getPointerInfo().getLocation().getX();
mouseY = MouseInfo.getPointerInfo().getLocation().getY();
x.setText("" + mouseX);
y.setText("" + mouseY);
}
}
}
I understand that this while-loop is the cause of the window crashing, but I can not figure out a way around it. Can anyone explain why I can not use a while-loop for JavaFX, as well as a way to solve this?
Your start() method don't have any change to exit the loop and therefore to return as you defined an infinite loop : while(true){...} without return statement.
Why not use a Timeline ?
Timeline timeLine = new Timeline(new KeyFrame(Duration.seconds(1), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
mouseX = MouseInfo.getPointerInfo().getLocation().getX();
mouseY = MouseInfo.getPointerInfo().getLocation().getY();
x.setText("" + mouseX);
y.setText("" + mouseY);
}
}));
timeLine.setCycleCount(Timeline.INDEFINITE);
timeLine.play();
or with a lambda :
Timeline timeLine = new Timeline(new KeyFrame(Duration.seconds(1),
e -> {
mouseX = MouseInfo.getPointerInfo().getLocation().getX();
mouseY = MouseInfo.getPointerInfo().getLocation().getY();
x.setText("" + mouseX);
y.setText("" + mouseY);
}
));
timeLine.setCycleCount(Timeline.INDEFINITE);
timeLine.play();
Another way to address your requirement could be using addEventFilter( MouseEvent.MOUSE_MOVED) on the StackPane object :
layout.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
x.setText("" + e.getScreenX());
y.setText("" + e.getScreenY());
});
The MouseEvent class provides both X and Y absolute position on the device and
on the source component :
getScreenX()
Returns absolute horizontal position of the event.
getScreenY()
Returns the absolute vertical y position of the event
getX();
Horizontal position of the event relative to the origin of the
MouseEvent's source.
getY();
Vertical position of the event relative to the origin of the
MouseEvent's source.
I'm trying to get a few shapes to "play nicely" with each other. I have 2 boxes next to each other and a cylinder "on top" of one of the boxes. I put them in a SubScene with a camera that can zoom in and out with a scrolling operation and pan by dragging. Here is my MCVE:
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Cylinder;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class MyApp extends Application {
DoubleProperty transX = new SimpleDoubleProperty();
DoubleProperty transY = new SimpleDoubleProperty();
DoubleProperty transZ = new SimpleDoubleProperty();
double startX, startY, curX, curY;
#Override
public void start(Stage stage) throws Exception {
Box box1 = new Box(30, 30, 5);
box1.setMaterial(new PhongMaterial(Color.RED));
Box box2 = new Box(30, 30, 5);
box2.setMaterial(new PhongMaterial(Color.GREEN));
box2.setTranslateX(32);
Cylinder cyn = new Cylinder(5, 15);
Group root = new Group(box1, box2, cyn);
root.getTransforms().addAll(new Translate(0, 0, 200),
new Rotate(-60, Rotate.X_AXIS),
new Rotate(-45, Rotate.Z_AXIS));
SubScene subs = new SubScene(root, 0, 0, true, SceneAntialiasing.BALANCED);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(1000);
camera.setNearClip(0);
camera.translateZProperty().bind(transZ);
camera.translateXProperty().bind(transX);
camera.translateYProperty().bind(transY);
subs.setCamera(camera);
Pane pane = new Pane(subs);
pane.setStyle("-fx-border-color: red;" +
"-fx-border-width: 3;");
subs.widthProperty().bind(pane.widthProperty());
subs.heightProperty().bind(pane.heightProperty());
pane.setOnScroll(e -> transZ.set(transZ.get() + e.getDeltaY() * 0.2));
pane.setOnMousePressed(e -> {
startX = curX = e.getX();
startY = curY = e.getY();
});
pane.setOnMouseDragged(e -> {
startX = curX;
startY = curY;
curX = e.getX();
curY = e.getY();
double deltaX = curX - startX;
double deltaY = curY - startY;
double deltaZ = deltaY * Math.sqrt(3);
transX.set(transX.get() - deltaX);
transY.set(transY.get() - deltaY);
transZ.set(transZ.get() + deltaZ);
});
Scene scene = new Scene(pane, 500, 500);
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
I have 2 problems here:
the Z order is specified by the order in which I put them in the group. I want the Z order to "Take care of itself". If I have shapes in space with specific coordinates and sizes and a camera at a specific position and angle I should be seeing a unique view without needing to specify the order. Here is a picture:
it looks like the green box is closer to the camera than the red box but my code doesn't imply this.
the cylinder moves relative to the box when the camera pans. In the image I mark in blue lines the location of the cylinder on the red box:
and now i pan the camera and get
I want the relative location of the shapes to not change as I pan the camera.
Edit: i removed this from the question and ask it in another one:
the shapes are shown on top of the border:
i thought that because the shapes are in the pane then pane's border will be higher in the Z order. I want the border to appear on top.
I guess it is actually a bug in JavaFX but at least it is easy to fix. Just don't set the near clipping pane of the camera to 0. Changing this line of your code to
camera.setNearClip(10);
will solve your first two problems.
Try to enable the Group Depth test with the following line:
setDepthTest(DepthTest.ENABLE);
I am trying to create balls whenever create button is clicked. I am able to create a single ball but for some reason not able to create multiple balls when the 'Create' button is clicked repeatedly.
Any help is appreciated.
package week3;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.TilePane;
import javafx.geometry.Orientation;
import javafx.geometry.Insets;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
public class BounceBallControl1 extends Application {
public final double radius = 10;
private double x = radius, y = radius;
private double dx = 1, dy = 1;
private Circle circle = new Circle(x, y, radius);
private Timeline animation;
#SuppressWarnings("restriction")
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
//BallPane ballPane = new BallPane();
Button btnCreate = new Button("Create");
Button btnDelete = new Button("Delete");
btnCreate.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
btnDelete.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
TilePane tileButtons = new TilePane(Orientation.HORIZONTAL);
tileButtons.setPadding(new Insets(5, 5, 5, 70));
tileButtons.setHgap(20.0);
tileButtons.getChildren().addAll(btnCreate, btnDelete);
Slider slSpeed = new Slider();
slSpeed.setMax(20);
//rateProperty().bind(slSpeed.valueProperty());
BorderPane pane = new BorderPane();
//pane.setCenter(ballPane);
pane.setTop(slSpeed);
pane.setBottom(tileButtons);
btnCreate.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
for(int i=0;i<=100;i++)
circle.setFill(Color.TURQUOISE); // Set ball color
pane.getChildren().add(circle); // Place a ball into this pane
// Create an animation for moving the ball
animation = new Timeline(
new KeyFrame(Duration.millis(50), (e -> {
// Check boundaries
if (x < radius || x > pane.getWidth() - radius) {
dx *= -1; // Change ball move direction
}
if (y < radius || y > pane.getHeight() - radius) {
dy *= -1; // Change ball move direction
}
// Adjust ball position
x += dx;
y += dy;
circle.setCenterX(x);
circle.setCenterY(y);
})));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
});
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 250, 250);
primaryStage.setTitle("BounceBallSlider"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
/*public DoubleProperty rateProperty() {
return animation.rateProperty();
}*/
}
You use the same instance of circle each time you click add button. So every time the same circle is added to the scene. Try by creating new circle each time, it will be ok.