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);
}
}
Related
I am using CSS to configure my JavaFX Sliders, then applying the style in code with:
cssSlider.getStyleClass().add("slider-style");
When I first open my window, the tick marks are present on the CSS configured Slider(s). When I close and reopen the window, the tick marks are no longer present.
This following example demonstrates the anomaly using 2 Sliders, one configured directly, the other via CSS. Click the button to hide the window for 2 seconds. Notice that the Slider in which I directly configure the attributes works fine after hiding and re-showing, but the CSS configured Slider loses its tick marks after hiding and re-showing.
Does anyone have any ideas why showing, hiding, and re-showing the window causes the tick marks to vanish from the CSS configured Slider? Am I doing something wrong, or is this a JavaFX bug?
sample.css:
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
CssExample.java:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
/**
* This simple example demonstrates that JavaFX Sliders configured with CSS only show their tick marks the first time
* they are shown. If the Slider is hidden, then shown again, the tick marks are gone forever.
*/
public class CssExample extends Application {
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Group root = new Group();
Scene scene = new Scene(root, 400, 200);
stage.setScene(scene);
stage.setTitle("Slider Sample");
scene.setFill(Color.BLACK);
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(10);
grid.setHgap(70);
scene.setRoot(grid);
int rowNumber = 1;
Label directLabel = new Label("Slider from attribute assignment");
GridPane.setConstraints(directLabel, 1, rowNumber++);
grid.getChildren().add(directLabel);
Slider directSlider = new Slider();
GridPane.setConstraints(directSlider, 1, rowNumber++);
grid.getChildren().add(directSlider);
directSlider.setShowTickMarks(true);
directSlider.setSnapToTicks(true);
directSlider.setMajorTickUnit(5);
directSlider.setMinorTickCount(5);
Label cssLabel = new Label("Slider from CSS (tick marks disappear after hidden)");
GridPane.setConstraints(cssLabel, 1, rowNumber++);
grid.getChildren().add(cssLabel);
Slider cssSlider = new Slider();
GridPane.setConstraints(cssSlider, 1, rowNumber++);
grid.getChildren().add(cssSlider);
URL url = getClass().getResource("sample.css");
String cssString = url.toExternalForm();
scene.getStylesheets().add(cssString);
cssSlider.getStyleClass().add("slider-style");
Button button = new Button("Hide for 2 Seconds");
GridPane.setConstraints(button, 1, rowNumber++);
grid.getChildren().add(button);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
stage.hide();
stage.show();
}
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
is this a JavaFX bug?
Yes.
See: https://github.com/openjdk/jfx/blob/fdc88341f1df8fb9c99356ada54b25124b77ea6e/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SliderSkin.java#L398
It is a bug in the internal implementation of the setShowTickMarks method of SliderSkin (verified in JavaFX 18.0.1).
Test case:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class CssExample extends Application {
private static final String CSS = // language=CSS
"""
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
""";
private static final String CSS_INLINE = "data:text/css," + CSS;
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Platform.setImplicitExit(false);
Slider cssSlider = new Slider();
cssSlider.showTickMarksProperty().addListener((observable, oldValue, newValue) ->
System.out.println(cssSlider.showTickMarksProperty())
);
cssSlider.getStyleClass().add("slider-style");
PauseTransition hideAnimation = new PauseTransition(Duration.seconds(2));
hideAnimation.setOnFinished(e -> stage.show());
Button hideWindow = new Button("Hide for 2 Seconds");
hideWindow.setOnAction(e -> {
stage.hide();
hideAnimation.play();
});
Button closeApp = new Button("Close app");
closeApp.setOnAction(e -> Platform.exit());
VBox layout = new VBox(
10,
cssSlider, hideWindow, closeApp
);
layout.setPadding(new Insets(10));
layout.setPrefSize(400, 120);
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS_INLINE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Test output:
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: false]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
It switches showTicks from true to false, and back to true, which triggers the bug.
In the current implementation for the setShowTicks method:
private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
showTickMarks = (ticksVisible || labelsVisible);
Slider slider = getSkinnable();
if (showTickMarks) {
if (tickLine == null) {
tickLine = new NumberAxis();
tickLine.setAutoRanging(false);
tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
tickLine.setUpperBound(slider.getMax());
tickLine.setLowerBound(slider.getMin());
tickLine.setTickUnit(slider.getMajorTickUnit());
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setMinorTickVisible(ticksVisible);
// add 1 to the slider minor tick count since the axis draws one
// less minor ticks than the number given.
tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
if (slider.getLabelFormatter() != null) {
tickLine.setTickLabelFormatter(stringConverterWrapper);
}
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
} else {
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setMinorTickVisible(ticksVisible);
}
}
else {
getChildren().clear();
getChildren().addAll(track, thumb);
// tickLine = null;
}
getSkinnable().requestLayout();
}
The first time it shows the ticks it will do this:
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
Then, when the ticks are hidden, it will do this:
getChildren().clear();
getChildren().addAll(track, thumb);
Then, when the ticks are supposed to be shown again, the tickLine is not added back to the children, so it never shows the ticks again.
This question already has answers here:
Using JavaFX with drag and drop, is it possible to have a ghost of the dragged object follow the cursor?
(2 answers)
JavaFX drag & drop with custom node beside mouse icon
(1 answer)
Closed 11 months ago.
GOAL:
I wish to change the white file icon that appears when dragging a node that has implemented setOnDragDetected. More specifically, I'm recreating chess in JavaFX, and want to have a chess piece image appear when dragging, instead of the current white file image, like as shown below.
CODE: I've created a minimal example:
package application;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
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.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
public class StackOverflowSample extends Application {
#Override
public void start(Stage primaryStage) {
try {
primaryStage.setTitle("Chess Sample");
Group root = new Group();
Scene scene = new Scene(root, 400, 200);
/* 1. Setting up Chess squares (aka stackpanes)*/
StackPane source1 = new StackPane(new Rectangle(100,100,Color.BLUE));
StackPane target1 = new StackPane(new Rectangle(100,100,Color.LIGHTBLUE));
source1.setLayoutX(50);
source1.setLayoutY(50);
target1.setLayoutX(250);
target1.setLayoutY(50);
/*2. Setting up the chess piece to move*/
Image whiteKnightImg = new Image("https://www.kindpng.com/picc/m/22-223299_white-knight-chess-png-transparent-png.png", 90,90,true,true);
ImageView whiteKnightView = new ImageView(whiteKnightImg);
source1.getChildren().add(whiteKnightView);
/* 3. Adding Drag and Drop Functionality*/
source1.setOnDragDetected(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
Dragboard db = source1.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.putString("hasPiece");
db.setContent(content);
event.consume();
}
});
target1.setOnDragOver(new EventHandler <DragEvent>() {
#Override
public void handle(DragEvent event) {
if (event.getGestureSource() != target1 && event.getDragboard().hasString()) {
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
}
});
target1.setOnDragDropped(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasString()) {
target1.getChildren().add(whiteKnightView);
success = true;
}
event.setDropCompleted(success);
event.consume();
}
});
source1.setOnDragDone(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
if (event.getTransferMode() == TransferMode.MOVE) {
System.out.println("success");
}
event.consume();
}
});
root.getChildren().addAll(source1,target1);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
POSSIBLE SOLUTIONS:
Java Source Code. Maybe there's a way to inherit the Node class that implement the dragging related methods. I took a look at the source code for JavaFX.scene.Node and found the section related to setOnDragDetected, but didn't look into it any further.
public final void setOnDragDetected(
EventHandler<? super MouseEvent> value) {
onDragDetectedProperty().set(value);
}
public final EventHandler<? super MouseEvent> getOnDragDetected() {
return (eventHandlerProperties == null)
? null : eventHandlerProperties.getOnDragDetected();
}
/**
* Defines a function to be called when drag gesture has been
* detected. This is the right place to start drag and drop operation.
* #return the event handler that is called when drag gesture has been
* detected
*/
public final ObjectProperty<EventHandler<? super MouseEvent>>
onDragDetectedProperty() {
return getEventHandlerProperties().onDragDetectedProperty();
}
It's not possible. I've considered that it's not something that can be changed. believe it's possible that it's impossible to change this white icon, so I might just change my approach entirely. Instead of using StackPanes as squares that also hold images, I'm considering this tutorial.
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;
}
})
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);
}
}
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!