setOnMouseDragged not working on browser view - java

I'm writing a simple application in java using JxBrowser engine but i'm stuck at the very beginning. In my code, there is an undecorated stage that i want to make it draggable. To do so, searched and found the following link:
How to drag undecorated window
So I set mousePressed and MouseDragged event on stackPane but only mousePressed event gets fired and mouseDragged event no way gets fired. Any idea of what's the problem?
Thanks in advance.
import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.javafx.BrowserView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
private static double xOffset = 0;
private static double yOffset = 0;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Platform.setImplicitExit(false);
Browser browser = new Browser();
BrowserView browserView = new BrowserView(browser);
StackPane pane = new StackPane();
pane.getChildren().add(browserView);
Scene scene = new Scene(pane, 380, 500);
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setScene(scene);
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("mouse pressed");
xOffset = primaryStage.getX() - event.getScreenX();
yOffset = primaryStage.getY() - event.getScreenY();
}
});
pane.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("mouse dragged");
primaryStage.setX(event.getScreenX() + xOffset);
primaryStage.setY(event.getScreenY() + yOffset);
}
});
primaryStage.show();
}
}

Since jxbrowser requires license I couldn't test it... So I replaced that with Label and it works fine.. So my guess is that you are trying to drag by clicking on the browser itself and not the StackPane.. Try clicking at the corner of the Stage or else add stack pane to VBox and setPadding to it.. And try clicking at the corner.. If your clicking on the browser then browser's mouse events will be trigerred..
Proof:
The correct code
package RezRem;
import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.javafx.BrowserView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
private static double xOffset = 0;
private static double yOffset = 0;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Platform.setImplicitExit(false);
Browser browser = new Browser();
BrowserView browserView = new BrowserView(browser);
StackPane pane = new StackPane();
pane.getChildren().add(browserView);
pane.setPadding(new Insets(10,10,10,10));
Scene scene = new Scene(pane, 380, 500);
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setScene(scene);
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("mouse pressed");
xOffset = primaryStage.getX() - event.getScreenX();
yOffset = primaryStage.getY() - event.getScreenY();
}
});
pane.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("mouse dragged");
primaryStage.setX(event.getScreenX() + xOffset);
primaryStage.setY(event.getScreenY() + yOffset);
}
});
browser.loadURL("http://www.google.com");
primaryStage.show();
}
}
Reason
The jxbrowser extends till the edge so the stack pane wasn't on top neither was it visible on the sides so the mouse listener's never got triggered by setting the padding of stack pane there was 10px gap on all four sides where, if clicked, triggered the mouse events and thereby solves the problem..

I ran into this same problem. I ended up solving it by using the setMouseEventsHandler method on BrowserView which does seem to receive all the events you need (although it does not seem to receive MouseEvent.MOUSE_DRAGGED events unless they have been rebranded as MouseEvent.MOUSE_MOVED events).
Rectangle[] dragIncludeRects = ...; // The area which is draggable (ie the title bar)
Rectangle[] dragExcludeRects = ...; // Exclusions (ie a close button on the title bar)
BrowserView popupView = ...;
JDialog popupFrame = ...; // could also be JFrame
Point dragOffset = null;
popupView.setMouseEventsHandler(new InputEventsHandler<MouseEvent>() {
public boolean handle(MouseEvent event) {
switch(event.getID()) {
case MouseEvent.MOUSE_PRESSED:
if (
dragIncludeRects.exists((rect) => rect.contains(event.getPoint())) &&
!dragExcludeRects.exists((rect) => rect.contains(event.getPoint()))
) {
dragOffset = SwingUtilities.convertPoint(
popupView, event.getPoint(), popupFrame);
} else {
dragOffset = null;
}
break;
case MouseEvent.MOUSE_RELEASED:
dragOffset = null;
break;
case MouseEvent.MOUSE_MOVED:
if (dragOffset != null) {
// Note I tried using the position from the event but it doesn't work well
val position = MouseInfo.getPointerInfo().getLocation();
popupFrame.setLocation(position.x - offset.x, position.y - offset.y);
}
}
return false;
}
})

Related

Javafx - Update progress indicator in UI from java class

The following are the changes I made in fxml
Changes in the java file , here my code :
private ProgressIndicator pi;
void handlebuildButtonAction(ActionEvent event) throws IOException, GeneralSecurityException {
if ((entServer.isSelected()==true || compasServer.isSelected()==true)) {
if(!fileList.isEmpty()){
ProgressIndicator pi = new ProgressIndicator();
pi.setProgress(10);
}
}
The progress indicator is not updated when I run the application. I'm not sure how to sync the changes to UI. Assist me on this. Thanks in advance.
output
For example: if you set 0.1 - progress will be 10%, 0.2 - 20% and so on, so when you set the progress => 1 you will always have "done".
Here, this an example with a button, when you click the button, your progress indicator will be updated(one click + 10%):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
public class Test extends Application {
private ProgressIndicator pi;
private double counter = 0;
public void start(Stage stage)
{
ProgressIndicator pi = new ProgressIndicator();
Button button = new Button("Press");
TilePane root = new TilePane();
// action event
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
counter += 0.1;
pi.setProgress(counter);
}
};
button.setOnAction(event);
root.getChildren().add(button);
root.getChildren().add(pi);
// create a scene
Scene scene = new Scene(root, 200, 200);
// set the scene
stage.setScene(scene);
stage.show();
}
public static void main(String args[])
{
// launch the application
launch(args);
}
}
Just change this code for your case:
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
if ((entServer.isSelected()==true || compasServer.isSelected()==true)) {
if (!fileList.isEmpty()) {
counter += 0.1;
pi.setProgress(counter);
}
}
}
};
Hope that helps you!

JavaFX - Undo drawing on a scaled Canvas

I'm developing a simple image editing functionality as a part of a larger JavaFX application, but I'm having some trouble to work out the undo/zoom and draw requirements together.
My requirements are the following:
The user should be able to:
Draw freehand on the image
Zoom in and out the image
Undo the changes
If the canvas is bigger than the window, it should have scroll-bars.
How I implemented these requirements:
The Drawing is done by starting a line when the mouse is pressed on the canvas, stroking it when it is dragged and closing the path when the button is released.
The Zoom works by scaling the canvas to a higher or lower value.
The Undo method takes a snapshot of the current state of the canvas when the mouse is pressed (before any change is made) and push it to a Stack of Images. When I need to undo some change I pop the last image of the Stack and draw it on the canvas, replacing the current image by the last one.
To have scroll-bars I just place the Canvas inside a Group and a ScrollPane.
Everything works fine, except when I try to draw on a scaled canvas. Due to the way I implemented the Undo functionality, I have to scale it back to 1, take a snapshot of the Node then scale it back to the size it was before. When this happens and the user is dragging the mouse the image position changes below the mouse pointer, causing it to draw a line that shouldn't be there.
Normal (unscaled canvas):
Bug (scaled canvas)
I tried the following approaches to solve the problem:
Don't re-scale to take the snapshot - Doesn't cause the unwanted line, but I end up with different image sizes in the stack, if it's smaller (zoomed out) when the snapshot was taken I now have a lower resolution of the image that I can't scale up without losing quality.
Tweak the logic and put the pushUndo call to the mouseReleased event - It almost worked, but when the user scrolled to a place and it's drawing there, the re-scaling causes the image to scroll back to the top-left;
Tried to search an way to "clone" or serialize the canvas and store the object state in the Stack - Didn't found anything I was able to adapt, and JavaFX doesn't support serialization of its objects.
I think the problem can be solved either by reworking the undo functionality as it doesn't need to re-scale the canvas to copy its state or by changing the way I zoom the canvas without scaling it, but I'm out of ideas on how to implement either of those options.
Below is the functional code example to reproduce the problem:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Stack;
public class Main extends Application {
Stack<Image> undoStack;
Canvas canvas;
double canvasScale;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
canvasScale = 1.0;
undoStack = new Stack<>();
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
hbox.getChildren().addAll(btnUndo, btnIncreaseZoom, btnDecreaseZoom);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
canvas = new Canvas();
canvas.setWidth(400);
canvas.setHeight(300);
group.getChildren().add(canvas);
scrollPane.setContent(group);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(2.0);
gc.setStroke(Color.RED);
canvas.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
canvas.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
});
canvas.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
});
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 800, 600);
stage.setScene(scene);
stage.show();
}
private void increaseZoom() {
canvasScale += 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void decreaseZoom () {
canvasScale -= 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void pushUndo() {
// Restore the canvas scale to 1 so I can get the original scale image
canvas.setScaleX(1);
canvas.setScaleY(1);
// Get the image with the snapshot method and store it on the undo stack
Image snapshot = canvas.snapshot(null, null);
undoStack.push(snapshot);
// Set the canvas scale to the value it was before the method
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void undo() {
if (!undoStack.empty()) {
Image undoImage = undoStack.pop();
canvas.getGraphicsContext2D().drawImage(undoImage, 0, 0);
}
}
}
Consider drawing Shape objects, in this case Path objects, and apply scale to them:
import java.util.Stack;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Main extends Application {
private Path path;
private Stack<Path> undoStack;
private Group group;
private double scale = 1;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
undoStack = new Stack<>();
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
HBox hbox = new HBox(4, btnUndo, btnIncreaseZoom, btnDecreaseZoom);
group = new Group();
BorderPane root = new BorderPane(new Pane(group), hbox, null,null, null);
Scene scene = new Scene(root, 300, 400);
root.setOnMousePressed(mouseEvent -> newPath(mouseEvent.getX(), mouseEvent.getY()));
root.setOnMouseDragged(mouseEvent -> addToPath(mouseEvent.getX(), mouseEvent.getY()));
primaryStage.setScene(scene);
primaryStage.show();
}
private void newPath(double x, double y) {
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
path.getElements().add(new MoveTo(x,y));
group.getChildren().add(path);
undoStack.add(path);
}
private void addToPath(double x, double y) {
path.getElements().add(new LineTo(x, y));
}
private void increaseZoom() {
scale += 0.1;
reScale();
}
private void decreaseZoom () {
scale -= 0.1;
reScale();
}
private void reScale(){
for(Path path : undoStack){
path.setScaleX(scale);
path.setScaleY(scale);
}
}
private void undo() {
if(! undoStack.isEmpty()){
Node node = undoStack.pop();
group.getChildren().remove(node);
}
}
}
I solved the problem by extending the Canvas component and adding a second canvas in the extended class to act as a copy of the main canvas.
Every time I made a change in the canvas I do the same change in this "carbon" canvas. When I need to re-scale the canvas to get the snapshot (the root of my problem) I just re-scale the "carbon" canvas back to 1 and get my snapshot from it. This doesn't cause the drag of the mouse in the main canvas, as it remains scaled during this process. Probably this isn't the optimal solution, but it works.
Below is the code for reference, to anyone who may have a similar problem in the future.
ExtendedCanvas.java
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import java.util.Stack;
public class ExtendedCanvas extends Canvas {
private final double ZOOM_SCALE = 0.1;
private final double MAX_ZOOM_SCALE = 3.0;
private final double MIN_ZOOM_SCALE = 0.2;
private double currentScale;
private final Stack<Image> undoStack;
private final Stack<Image> redoStack;
private final Canvas carbonCanvas;
private final GraphicsContext gc;
private final GraphicsContext carbonGc;
public ExtendedCanvas(double width, double height){
super(width, height);
carbonCanvas = new Canvas(width, height);
undoStack = new Stack<>();
redoStack = new Stack<>();
currentScale = 1.0;
gc = this.getGraphicsContext2D();
carbonGc = carbonCanvas.getGraphicsContext2D();
setEventHandlers();
}
private void setEventHandlers() {
this.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.beginPath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
this.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
});
this.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
carbonGc.closePath();
});
}
public void zoomIn() {
if (currentScale < MAX_ZOOM_SCALE ) {
currentScale += ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomOut() {
if (currentScale > MIN_ZOOM_SCALE) {
currentScale -= ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomNormal() {
currentScale = 1.0;
setScale(currentScale);
}
private void setScale(double value) {
this.setScaleX(value);
this.setScaleY(value);
carbonCanvas.setScaleX(value);
carbonCanvas.setScaleY(value);
}
private void pushUndo() {
redoStack.clear();
undoStack.push(getSnapshot());
}
private Image getSnapshot(){
carbonCanvas.setScaleX(1);
carbonCanvas.setScaleY(1);
Image snapshot = carbonCanvas.snapshot(null, null);
carbonCanvas.setScaleX(currentScale);
carbonCanvas.setScaleY(currentScale);
return snapshot;
}
public void undo() {
if (hasUndo()) {
Image redo = getSnapshot();
redoStack.push(redo);
Image undoImage = undoStack.pop();
gc.drawImage(undoImage, 0, 0);
carbonGc.drawImage(undoImage, 0, 0);
}
}
public void redo() {
if (hasRedo()) {
Image undo = getSnapshot();
undoStack.push(undo);
Image redoImage = redoStack.pop();
gc.drawImage(redoImage, 0, 0);
carbonGc.drawImage(redoImage, 0, 0);
}
}
public boolean hasUndo() {
return !undoStack.isEmpty();
}
public boolean hasRedo() {
return !redoStack.isEmpty();
}
}
Main.java
package com.felipepaschoal;
import javafx.application.Application;
import javafx.scene.Group;
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.stage.Stage;
public class Main extends Application {
ExtendedCanvas extendedCanvas;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> extendedCanvas.undo());
Button btnRedo = new Button("Redo");
btnRedo.setOnAction(actionEvent -> extendedCanvas.redo());
Button btnDecreaseZoom = new Button("-");
btnDecreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomOut());
Button btnResetZoom = new Button("Reset");
btnResetZoom.setOnAction(event -> extendedCanvas.zoomNormal());
Button btnIncreaseZoom = new Button("+");
btnIncreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomIn());
hbox.getChildren().addAll(
btnUndo,
btnRedo,
btnDecreaseZoom,
btnResetZoom,
btnIncreaseZoom
);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
extendedCanvas = new ExtendedCanvas(300,200);
group.getChildren().add(extendedCanvas);
scrollPane.setContent(group);
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 600, 400);
stage.setScene(scene);
stage.show();
}
}

JavaFx: check if the mouse is on node's children

I would like to know if there is a way to determine if the mouse collides with a node's children, in less words, In the example below, If I click on the Group the output is:
"Group!"
If I click on the image the output is:
"Group!
Image!"
Is there a way to put code in the "group.setOnMousePressed" in order to check if the mouse in on the image and in that case don't do anything and just execute what is in the "group.setOnMousePressed", in order to have this output clicking on image:
"Image!"
Please find below a SSCCE:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class SSCCEForSO extends Application {
#Override
public void start(Stage primaryStage) {
AnchorPane anchor= new AnchorPane();
Group group= new Group();
ImageView image= new ImageView();
image.setImage(ImageUtil.getImage("wave.png"));
ImageView image2= new ImageView();
image2.setImage(ImageUtil.getImage("pause15.png"));
HBox hBox = new HBox();
hBox.setPrefSize(200, 200);
hBox.setAlignment(Pos.CENTER);
hBox.setStyle("-fx-padding: 10;-fx-background-color: firebrick;-fx-background-radius: 5;");
hBox.getChildren().add( image);
hBox.getChildren().add( image2);
group.getChildren().add(hBox);
group.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("Group!");
}
});
image2.onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("Image!");
}
});
anchor.getChildren().add(group);
Scene scene = new Scene(anchor, 800, 600);
primaryStage.setScene( scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Thank you in advance
Suggested Solution: Consume the Event
Consume the mouse event when you handle it in your on clicked handler for the image:
image2.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
mouseEvent.consume();
System.out.println("Image! " + mouseEvent.getTarget());
}
});
This will prevent the event from continuing to bubble up the event dispatch chain. Read the section of the Oracle JavaFX documentation on handling events if you need to understand what this actually means.
Alternate Solution: Check the Event Target
Note, I also added mouseEvent.getTarget() to the handler. You can use the result of this call to evaluate the target of the event and take action based upon that. For example, the following code would also work:
hBox.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getTarget() == hBox) {
System.out.println("hBox! " + mouseEvent.getTarget());
} else {
System.out.println("hBox Ignored! " + mouseEvent.getTarget());
}
}
});
image2.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("Image! " + mouseEvent.getTarget());
}
});
Notes for the above code:
I changed the handler settings to all consistently use onMouseClicked rather than one onMousePressed handler and one onMouseClicked handler. This is important because mouse clicks and mouse presses are distinct events.
I used set the onClickHandler on the hBox rather than the enclosing group because the hBox is actually the target of the event rather than the enclosing group. The hBox covers the group completely, so the user cannot directly click on the group as an event target, they can only click on the covering hBox.
Executable Sample
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class SSCCEForSO extends Application {
#Override
public void start(Stage primaryStage) {
AnchorPane anchor= new AnchorPane();
Group group= new Group();
ImageView image= new ImageView();
image.setImage(new Image("http://icons.iconarchive.com/icons/custom-icon-design/pretty-social-media-2/64/Google-wave-icon.png"));
ImageView image2= new ImageView();
image2.setImage(new Image("http://icons.iconarchive.com/icons/custom-icon-design/pretty-office-8/64/Pause-icon.png"));
HBox hBox = new HBox();
hBox.setPrefSize(200, 200);
hBox.setAlignment(Pos.CENTER);
hBox.setStyle("-fx-padding: 10;-fx-background-color: firebrick;-fx-background-radius: 5;");
hBox.getChildren().add( image);
hBox.getChildren().add( image2);
group.getChildren().add(hBox);
group.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("Group!" + mouseEvent.getSource());
}
});
image2.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("Image!" + mouseEvent.getSource());
mouseEvent.consume();
}
});
anchor.getChildren().add(group);
Scene scene = new Scene(anchor, 800, 600);
primaryStage.setScene( scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Drag and dropping with visual indication

I am trying to implement a drag and drop between 2 objects.
The problem is when I drag from one rectangle to the other I need the start of a line to be connected on the source rectangle and the end of the line to follow the mouse around.
Then when I drop at the second rectangle the end of the line should bind at it and a string passed.
On one hand using onmouseclicked, dragged, and released I managed to make the line follow the mouse around but am not able to make the second rectangle understand that the mouse was released on it (as shown in the code below)
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Test extends Application {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Pane root = new BorderPane();
Rectangle rect1=new Rectangle(100,50);
rect1.setFill(Color.AQUAMARINE);rect1.setStroke(Color.BLACK);
Label rect1_label=new Label("Rectangle 1");
rect1_label.setLayoutX(20);rect1_label.setLayoutY(15);
rect1.setLayoutX(220);rect1.setLayoutY(240);
Line line=new Line (rect1.getLayoutX()+rect1.getWidth(),rect1.getLayoutY()+rect1.getHeight()/2,
rect1.getLayoutX()+rect1.getWidth(),rect1.getLayoutY()+rect1.getHeight()/2);
line.startXProperty().bind(rect1.translateXProperty().add(rect1.getLayoutX()+rect1.getWidth()));
line.startYProperty().bind(rect1.translateYProperty().add(rect1.getLayoutY()+rect1.getHeight()/2));
line.getStrokeDashArray().setAll(10.0, 5.0);
Rectangle rect2=new Rectangle(100,50);
rect2.setFill(Color.BISQUE);rect2.setStroke(Color.BLACK);
Label rect2_label=new Label("Rectangle 2");
rect2_label.setLayoutX(20);rect2_label.setLayoutY(15);
rect2.setLayoutX(600);rect2.setLayoutY(240);
root.getChildren().addAll(rect1,rect2,line);
rect1.setOnMousePressed(new EventHandler<MouseEvent>(){
public void handle(MouseEvent event){
line.setVisible(true);
line.toBack();
line.setEndX(event.getX());
line.setEndY(event.getY());
orgSceneX = event.getSceneX();
orgSceneY = event.getSceneY();
orgTranslateX = ((Rectangle)(event.getSource())).getTranslateX();
orgTranslateY = ((Rectangle)(event.getSource())).getTranslateY();
line.setEndX(event.getSceneX());
line.setEndY(event.getSceneY());
}
});
rect1.setOnMouseDragged(new EventHandler<MouseEvent>(){
public void handle(MouseEvent event)
{
double offsetX = event.getSceneX();
double offsetY = event.getSceneY();
double newTranslateX = offsetX;
double newTranslateY = offsetY;
line.setEndX(newTranslateX);
line.setEndY(newTranslateY);
}
});
rect1.setOnMouseReleased(new EventHandler<MouseEvent>(){
public void handle(MouseEvent event)
{
line.setVisible(false);
}
});
Scene scene = new Scene(root);
primaryStage.setTitle("Nodes test 1");
primaryStage.setMinWidth(1000);
primaryStage.setMinHeight(600);
primaryStage.setScene(scene);
primaryStage.show();
}
}
.
I also know how to drag and drop a value or string from one object to another like that
.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Test2 extends Application {
double orgSceneX, orgSceneY;
double orgTranslateX, orgTranslateY;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Pane root = new BorderPane();
Rectangle rect1=new Rectangle(100,50);
rect1.setFill(Color.AQUAMARINE);rect1.setStroke(Color.BLACK);
Label rect1_label=new Label("Rectangle 1");
rect1_label.setLayoutX(20);rect1_label.setLayoutY(15);
rect1.setLayoutX(220);rect1.setLayoutY(240);
Line line=new Line (rect1.getLayoutX()+rect1.getWidth(),rect1.getLayoutY()+rect1.getHeight()/2,
rect1.getLayoutX()+rect1.getWidth(),rect1.getLayoutY()+rect1.getHeight()/2);
line.startXProperty().bind(rect1.translateXProperty().add(rect1.getLayoutX()+rect1.getWidth()));
line.startYProperty().bind(rect1.translateYProperty().add(rect1.getLayoutY()+rect1.getHeight()/2));
line.getStrokeDashArray().setAll(10.0, 5.0);
Rectangle rect2=new Rectangle(100,50);
rect2.setFill(Color.BISQUE);rect2.setStroke(Color.BLACK);
Label rect2_label=new Label("Rectangle 2");
rect2_label.setLayoutX(20);rect2_label.setLayoutY(15);
rect2.setLayoutX(600);rect2.setLayoutY(240);
root.getChildren().addAll(rect1,rect2,line);
rect1.setOnDragDetected(new EventHandler<MouseEvent>(){
#Override public void handle(MouseEvent event){
Dragboard db = rect1.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.putString("rec");
db.setContent(content);
event.consume();
}
});
rect2.setOnDragOver(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
rect2.setOnDragDropped(new EventHandler<DragEvent>(){
#Override public void handle(DragEvent event){
final Dragboard db = event.getDragboard();
if (db.getString().equals("rec")){
System.out.println("Accepted");
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
}
});
Scene scene = new Scene(root);
primaryStage.setTitle("Nodes test 1");
primaryStage.setMinWidth(1000);
primaryStage.setMinHeight(600);
primaryStage.setScene(scene);
primaryStage.show();
}
}
If I use them both something goes wrong
What can I do in order for the line to be drawn and the value to be passed at the same time?
Activate a "full press-drag-release gesture" by calling startFullDrag on the node where the drag originated. The differences between the types of drag gesture are detailed in the Javadocs for MouseEvent, but basically this allows mouse events to be delivered to nodes other than the node that originated the drag while the drag is in process.
Note, though, that you don't get a mouseReleased event on a node unless the mouse was pressed on that node (as far as I can tell). So you need to do a little bit of work to figure out if the release occurred over the second rectangle. I did this by setting a flag if the mouse entered the node during the drag and set it to false if it exits. You could also probably do this by looking for a mouse release on the underlying container, and seeing if the bounds of the node included the coordinates of the mouse event.
Also note in this example that I needed to call line.setMouseTransparent(true); to make sure the line didn't consume the mouse events.
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class DragAndDropWithLine extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Rectangle rect1 = new Rectangle(50, 50, 50, 100);
rect1.setFill(Color.YELLOW);
Rectangle rect2 = new Rectangle(200, 50, 50, 100);
rect2.setFill(Color.BLUE);
Line line = new Line();
line.setMouseTransparent(true);
pane.getChildren().addAll(rect1, rect2);
BooleanProperty dragging = new SimpleBooleanProperty();
BooleanProperty draggingOverRect2 = new SimpleBooleanProperty();
rect1.setOnDragDetected(event -> {
rect1.startFullDrag();
Point2D mouseSceneCoords = new Point2D(event.getSceneX(), event.getSceneY());
Point2D mousePaneCoords = pane.sceneToLocal(mouseSceneCoords);
line.setStartX(mousePaneCoords.getX());
line.setStartY(mousePaneCoords.getY());
line.setEndX(mousePaneCoords.getX());
line.setEndY(mousePaneCoords.getY());
pane.getChildren().add(line);
dragging.set(true);
});
pane.setOnMouseDragged(event -> {
if (dragging.get()) {
line.setEndX(event.getX());
line.setEndY(event.getY());
}
});
rect1.setOnMouseReleased(event -> {
if (draggingOverRect2.get()) {
pane.getChildren().remove(rect1);
rect2.setFill(Color.GREEN);
}
dragging.set(false);
draggingOverRect2.set(false);
pane.getChildren().remove(line);
});
rect2.setOnMouseDragEntered(event -> {
if (dragging.get()) {
draggingOverRect2.set(true);
}
});
rect2.setOnMouseDragExited(event -> draggingOverRect2.set(false));
primaryStage.setScene(new Scene(pane, 300, 200));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

How to make the MenuItems in a JavaFX context menu support an onMouseOver event

I'm looking for some guidance on how to proceed with a problem I'm having. I hava a JavaFX scene and within it some nodes (shapes) that connect to each other with one or more lines. I can right-click on a shape to bring up a context menu. Let's say this particular shape that was just right-clicked has 3 lines coming out of it (call them line1, line2, line3) and you want to use the context menu to delete one. You can select "line2" for example, and it will fire the onAction event to remove that line. That all works fine.
The trouble is, you don't know which of the 3 lines on the screen is line1 or line2 or line3 (unless of course they are labeled) and so you don't know which one you are about to remove until you remove it. What I would really like to do, for example, is to place my mouse over "line2" in the context menu and have line2 in the scene change color or something to indicate that it is the one about to be deleted (before I click the mouse). However, the only event I see supported by MenuItem is the onAction event for when it is clicked. Is there some way to give it onMouseOver functionality? if not, how could this feature be implemented?
Thanks!
Try this SSCCE:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ContextMenuDemo extends Application {
private DropShadow ds = new DropShadow();
#Override
public void start(Stage primaryStage) {
final Line line1 = new Line(60, 10, 150, 10);
final Line line2 = new Line(60, 30, 150, 50);
final Line line3 = new Line(60, 60, 150, 90);
final ContextMenu cm = new ContextMenu();
cm.getItems().add(getMenuItemForLine("line 1", line1));
cm.getItems().add(getMenuItemForLine("line 2", line2));
cm.getItems().add(getMenuItemForLine("line 3", line3));
final Rectangle rectangle = new Rectangle(70, 70, Color.TAN);
rectangle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getButton() == MouseButton.SECONDARY) {
cm.show(rectangle, e.getScreenX(), e.getScreenY());
}
}
});
Group root = new Group();
root.getChildren().addAll(rectangle, line1, line2, line3);
Scene scene = new Scene(root, 300, 250);
// load style of modified paddings for menuitems
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private MenuItem getMenuItemForLine(String menuName, final Line line) {
Label menuLabel = new Label(menuName);
// apply style to occupy larger space for label
menuLabel.setStyle("-fx-padding: 5 10 5 10");
MenuItem mi = new MenuItem();
mi.setGraphic(menuLabel);
line.setStroke(Color.BLUE);
menuLabel.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
line.setStroke(Color.RED);
line.setEffect(ds);
}
});
menuLabel.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
line.setStroke(Color.BLUE);
line.setEffect(null);
}
});
return mi;
}
public static void main(String[] args) {
launch(args);
}
}
with style.css
.menu-item {
/* -fx-skin: "com.sun.javafx.scene.control.skin.MenuItemSkin";*/
-fx-background-color: transparent;
-fx-padding: 0em; /* do not pad for item. we want to ccupy all spaces for graphics only */
}
.menu-item:focused {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-text-fill: -fx-selection-bar-text;
}
.menu-item .graphic-container {
-fx-padding: 0em; /* do not pad for graphics, label graphic pads itself */
}
.menu-item .label {
-fx-padding: 0em; /* do not pad for label, since there is no label text set */
-fx-text-fill: -fx-text-base-color;
}
Screenshot:
Description:
This is somewhat a bug that MenuItem does not work for MenuItem.addEventHandler(MouseEvent.MOUSE_ENTERED, ...) I think. As a workaround, we define new Label, register event handlers to it and set it as a graphic of menu item while the text(label) of menuitem intentionally left an empty. But the graphic of menu item does not (by default) occupy all space of menu item, so mouse events are not handled properly at the edges of menu item. To overcome this problem we reset all paddings of menuitem, menuitem's label and graphic through css. You can observe this by commenting out the style loading in the above code.
Here is a sample App I just created on an aproach to identify the lines:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class MainTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
// TODO Auto-generated method stub
AnchorPane anchorPane = new AnchorPane();
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
Line linea = new Line(0, 0, 50, 50);
linea.setFill(Color.BLACK);
final Tooltip t = new Tooltip("Line 1");
linea.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Line line = (Line) event.getSource();
line.setStroke(Color.RED);
t.show((Line) event.getSource(), event.getScreenX(),
event.getScreenY());
}
});
linea.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Line line = (Line) event.getSource();
line.setStroke(Color.BLACK);
t.hide();
}
});
anchorPane.getChildren().add(linea);
stage.show();
}
}
Hope it helps!

Categories

Resources