Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
JavaFx: I have a HBox with a Button in it ,inside a Group, and I would like to manage the "MousePressed" event differently for each node.
At the moment if I handle the same event for all the nodes, it is always catches by the parent node (Group).
Is there a way to do that?
For example is there a way to determine if the mouse coordinates are over the node's children (HBox in my example)?
This is an example of what I need:
If I click on Group I want to hide the HBox if the coordinate of mouse don't collide with HBox, If I click on HBox, Hbox doesn't need to be hide, and If I click on the Button (implemented as an ImageView) I need to execute code without hide the HBox.
So for example what I would like to do is something like that:
'
Group group= new Group();
HBox hBox = new HBox();
ImageView image= new ImageView();
hBox.getChildren().add(image);
hBox.toFront();
group.getChildren().add(hBox);
image.setImage(ImageUtil.getImage("img.png"));
group.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
<if not collide with HBOX hide HBOX.>
}
});
box.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
<do other things without hidding HBOX.>
}
});
image.onMouseClickedProperty().set(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
<do something>
}
});
'
Where group is inside an AncorPane designed with sceneBuilder
'
#FXML
Group group;
'
Thank you for your help.
Your question is unclear, but nontheless, if it helps you:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
// the magic: use setPickOnBounds(false);
public class LayersWithMouseEvents extends Application {
#Override
public void start(Stage primaryStage) {
// root
StackPane root = new StackPane();
// layers
Pane bottomLayer = new Pane();
Pane topLayer = new Pane();
// bottom: layer1, top: layer2
root.getChildren().addAll(bottomLayer, topLayer);
// layer 1 objects
Rectangle rectangle = new Rectangle( 100,100,100,100);
rectangle.setFill(Color.GREEN);
rectangle.setOnMousePressed(e -> System.out.println("Rectangle 0: " + e));
bottomLayer.getChildren().add(rectangle);
// layer 2 objects
rectangle = new Rectangle( 50,50,50,50);
rectangle.setFill(Color.RED);
rectangle.setOnMousePressed(e -> System.out.println("Rectangle 1: " + e));
topLayer.getChildren().add(rectangle);
rectangle = new Rectangle( 125,125,50,50);
rectangle.setFill(Color.RED);
rectangle.setOnMousePressed(e -> System.out.println("Rectangle 2: " + e));
topLayer.getChildren().add(rectangle);
rectangle = new Rectangle( 200,200,50,50);
rectangle.setFill(Color.RED);
rectangle.setOnMousePressed(e -> System.out.println("Rectangle 3: " + e));
topLayer.getChildren().add(rectangle);
// layer 1 event handler
bottomLayer.setOnMousePressed(e -> System.out.println("Layer 1: " + e));
// layer 2 event handler
topLayer.setOnMousePressed(e -> System.out.println("Layer 2: " + e));
// this is the magic that allows you to click on the layer1 object event though layer 2 is on top of layer 1
// but ONLY(!) as long as the layer is transparent. if you add e. g. this it won't work anymore to click through to layer 1:
// layer2.setStyle( "-fx-background-color:yellow");
topLayer.setPickOnBounds(false);
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Another example would be to add the listener to the parent, e. g. the scene and check the event's target like this:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class LayersWithMouseEvents2 extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rectangle = new Rectangle( 100,100,100,100);
rectangle.setFill(Color.GREEN);
HBox hBox = new HBox();
hBox.setPrefSize(200, 200);
hBox.setStyle("-fx-background-color:yellow");
hBox.getChildren().add( rectangle);
Group root = new Group();
root.getChildren().add(hBox);
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
primaryStage.getScene().setOnMousePressed(e -> System.out.println("Scene: " + e));
}
public static void main(String[] args) {
launch(args);
}
}
Depending on where you click you get:
Scene: MouseEvent [source = javafx.scene.Scene#2c69fd61, target = javafx.scene.Scene#2c69fd61, eventType = MOUSE_PRESSED, consumed = false, x = 295.0, y = 134.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = null, point = Point3D [x = 295.0, y = 134.0, z = 0.0], distance = 1119.6152422706632]
Scene: MouseEvent [source = javafx.scene.Scene#2c69fd61, target = HBox#5a40dda8, eventType = MOUSE_PRESSED, consumed = false, x = 148.0, y = 134.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = HBox#5a40dda8, point = Point3D [x = 148.0, y = 134.0, z = 0.0], distance = 1119.6152422706632]
Scene: MouseEvent [source = javafx.scene.Scene#2c69fd61, target = Rectangle[x=100.0, y=100.0, width=100.0, height=100.0, fill=0x008000ff], eventType = MOUSE_PRESSED, consumed = false, x = 52.0, y = 54.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = Rectangle[x=100.0, y=100.0, width=100.0, height=100.0, fill=0x008000ff], point = Point3D [x = 152.0, y = 154.0, z = 0.0], distance = 1119.6152422706632]
And with the visibility toggling it could be like this:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class LayersWithMouseEvents2 extends Application {
boolean hBoxVisible = true;
#Override
public void start(Stage primaryStage) {
Rectangle rectangle = new Rectangle( 100,100,100,100);
rectangle.setFill(Color.GREEN);
HBox hBox = new HBox();
hBox.setPrefSize(200, 200);
hBox.setStyle("-fx-background-color:yellow");
hBox.getChildren().add( rectangle);
Group root = new Group();
root.getChildren().add(hBox);
Scene scene = new Scene(root, 800, 600);
scene.setOnMousePressed(e -> {
System.out.println("Scene: " + e);
if( e.getTarget() == hBox) {
System.out.println( "HBox clicked");
}
if( e.getTarget() == rectangle) {
System.out.println( "Rectangle clicked");
}
if( e.getTarget() == scene) {
System.out.println( "Scene clicked");
hBoxVisible = !hBoxVisible;
hBox.setVisible(hBoxVisible);}
});
primaryStage.setScene( scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
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();
}
}
I have some JavaFX Popup in my application. And when any of these popups is foucsed, I need it bring on top of every other popups regardless of it's index in Window.getWindows().
I've tried to call method like toFront but it's not in Popup class. I've also tried to change index of focused Popup in Window.getWindows() but that also didn't worked because I don't know how to interchange index of two elements in a ObservableList.
e.g.
Let's say I have two Popup called p1 and p2 and in each I have nodes n1 and n2 respectively which are used to move these popup, So whenever n1 is dragged p1 should come on top and when n2 is dragged p2 should come on top.
Here is my minimal example:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class Example extends Application{
public static void main(String... arguments){
launch(arguments);
}
public void applyTo(Pane node, Popup parent){
final double[] dragDelta = new double[2];
node.setOnMousePressed(e -> {
dragDelta[0] = parent.getX() - e.getScreenX();
dragDelta[1] = parent.getY() - e.getScreenY();
//code to bring parent Popup to front
});
node.setOnMouseDragged(e -> {
parent.setX(e.getScreenX() + dragDelta[0]);
parent.setY(e.getScreenY() + dragDelta[1]);
});
}
#Override
public void start(Stage primaryStage) throws Exception{
Button b1 = new Button("Open p1");
Button b2 = new Button("Open p2");
HBox n1 = new HBox(new Label("This is p1"));
HBox n2 = new HBox(new Label("This is p2"));
n1.setMinSize(200, 120);
n2.setMinSize(200, 120);
n1.setStyle("-fx-background-color: blue; -fx-background-radius: 4px;");
n2.setStyle("-fx-background-color: red; -fx-background-radius: 4px;");
n1.setAlignment(Pos.CENTER);
n2.setAlignment(Pos.CENTER);
Popup p1 = new Popup();
Popup p2 = new Popup();
p1.getContent().add(n1);
p2.getContent().add(n2);
applyTo(n1, p1);
applyTo(n2, p2);
b1.setOnAction(event -> {
if(!p1.isShowing()) p1.show(primaryStage);
else p1.hide();
});
b2.setOnAction(event -> {
if(!p2.isShowing()) p2.show(primaryStage);
else p2.hide();
});
HBox root = new HBox(10, b1, b2);
root.setAlignment(Pos.CENTER);
primaryStage.setScene(new Scene(root, 500, 200));
primaryStage.show();
}
}
So what is the solution for this problem?
For some reason I don't understand, toFront/back is only implemented on Stage, not on its parent classes even though the actual collaborator that manages the stacking is already available in Window:
The implementation in Stage:
/**
* Bring the {#code Window} to the foreground. If the {#code Window} is
* already in the foreground there is no visible difference.
*/
public void toFront() {
if (getPeer() != null) {
getPeer().toFront();
}
}
getPeer() is a package-private method in Window that returns the internal class TKStage. So if you are allowed to go dirty (because accessing an internal class and having to access via reflection - all with the usual loud "Beware"!) would be:
protected void toFront(Popup popup) {
// use your favorite utility method to invoke a method
TKStage peer = (TKStage) FXUtils.invokeGetMethodValue(Window.class, popup, "getPeer");
if (peer != null) {
peer.toFront();
}
}
Requires to export/open not-exported packages in javafx.graphics - compiler and runtime errors will guide you (my context is heavily tweaked anyway, so don't know exactly which are added by this)
Here is the solution with stages it is the only work around I have found at all even though you hate the idea of having multiple stages if you want the functionality this is it. If you decide to stick with leaving them in the background thats cool too. An idea to solve your too may stages dilemma is to use a queue of stages remove when in use and if all are shown add a new one when a stage is hidden send to the end of the queue
public class Example extends Application {
public void applyTo(Pane node, Stage parent, Stage primaryStage){
final double[] dragDelta = new double[2];
node.setOnMousePressed(e -> {
dragDelta[0] = parent.getX() - e.getScreenX();
dragDelta[1] = parent.getY() - e.getScreenY();
//code to bring parent Popup to front
});
node.setOnMouseDragged(e -> {
parent.setX(e.getScreenX() + dragDelta[0]);
parent.setY(e.getScreenY() + dragDelta[1]);
primaryStage.requestFocus();
});
node.setOnMouseReleased(event -> {
primaryStage.requestFocus();
});
}
#Override
public void start(Stage primaryStage) throws Exception{
Button b1 = new Button("Open p1");
Button b2 = new Button("Open p2");
HBox n1 = new HBox(new Label("This is p1"));
HBox n2 = new HBox(new Label("This is p2"));
n1.setMinSize(200, 120);
n2.setMinSize(200, 120);
n1.setStyle("-fx-background-color: blue; -fx-background-radius: 4px;");
n2.setStyle("-fx-background-color: red; -fx-background-radius: 4px;");
n1.setAlignment(Pos.CENTER);
n2.setAlignment(Pos.CENTER);
Stage p1 = new Stage(StageStyle.UNDECORATED);
Stage p2 = new Stage(StageStyle.UNDECORATED);
p1.setAlwaysOnTop(true);
p2.setAlwaysOnTop(true);
p1.setScene(new Scene(n1));
p2.setScene(new Scene(n2));
applyTo(n1, p1, primaryStage);
applyTo(n2, p2, primaryStage);
b1.setOnAction(event -> {
if(!p1.isShowing()) {
p1.show();
primaryStage.requestFocus();
}
else
p1.hide();
});
b2.setOnAction(event -> {
if(!p2.isShowing()) {
p2.show();
primaryStage.requestFocus();
}
else
p2.hide();
});
HBox root = new HBox(10, b1, b2);
root.setAlignment(Pos.CENTER);
primaryStage.setScene(new Scene(root, 500, 200));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
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'm using this to make a iOS-themed JavaFX2 (Java7) application with a frosted glass effect. The problem is that this code uses its effect on an ImageView. I'd like it to use its effect on whatever's behind the window, like this:
Is there anyway to do that? I'd also like that small drop-shadow effect you see around the above image.
To be clear, I don't want that slider or anything, just the effect of being able to see through the window and having that slight shadow around the edges. I want to use this iOS7-ish effect instead of aero, though.
This might be important: I'm using a modified version of Undecorator.
import javafx.animation.*;
import javafx.application.*;
import javafx.beans.property.*;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.effect.*;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
public class FrostyTech extends Application {
private static final double BLUR_AMOUNT = 10;
private static final Effect frostEffect =
new BoxBlur(BLUR_AMOUNT, BLUR_AMOUNT, 3);
private static final ImageView background = new ImageView();
private static final StackPane layout = new StackPane();
#Override public void start(Stage stage) {
layout.getChildren().setAll(background, createContent());
layout.setStyle("-fx-background-color: null");
Scene scene = new Scene(
layout,
200, 300,
Color.TRANSPARENT
);
Platform.setImplicitExit(false);
scene.setOnMouseClicked(event -> {
if (event.getClickCount() == 2) Platform.exit();
});
makeSmoke(stage);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
background.setImage(copyBackground(stage));
background.setEffect(frostEffect);
makeDraggable(stage, layout);
}
// copy a background node to be frozen over.
private Image copyBackground(Stage stage) {
final int X = (int) stage.getX();
final int Y = (int) stage.getY();
final int W = (int) stage.getWidth();
final int H = (int) stage.getHeight();
try {
java.awt.Robot robot = new java.awt.Robot();
java.awt.image.BufferedImage image = robot.createScreenCapture(new java.awt.Rectangle(X, Y, W, H));
return SwingFXUtils.toFXImage(image, null);
} catch (java.awt.AWTException e) {
System.out.println("The robot of doom strikes!");
e.printStackTrace();
return null;
}
}
// create some content to be displayed on top of the frozen glass panel.
private Label createContent() {
Label label = new Label("Create a new question for drop shadow effects.\n\nDrag to move\n\nDouble click to close");
label.setPadding(new Insets(10));
label.setStyle("-fx-font-size: 15px; -fx-text-fill: green;");
label.setMaxWidth(250);
label.setWrapText(true);
return label;
}
// makes a stage draggable using a given node.
public void makeDraggable(final Stage stage, final Node byNode) {
final Delta dragDelta = new Delta();
byNode.setOnMousePressed(mouseEvent -> {
// record a delta distance for the drag and drop operation.
dragDelta.x = stage.getX() - mouseEvent.getScreenX();
dragDelta.y = stage.getY() - mouseEvent.getScreenY();
byNode.setCursor(Cursor.MOVE);
});
final BooleanProperty inDrag = new SimpleBooleanProperty(false);
byNode.setOnMouseReleased(mouseEvent -> {
byNode.setCursor(Cursor.HAND);
if (inDrag.get()) {
stage.hide();
Timeline pause = new Timeline(new KeyFrame(Duration.millis(50), event -> {
background.setImage(copyBackground(stage));
layout.getChildren().set(
0,
background
);
stage.show();
}));
pause.play();
}
inDrag.set(false);
});
byNode.setOnMouseDragged(mouseEvent -> {
stage.setX(mouseEvent.getScreenX() + dragDelta.x);
stage.setY(mouseEvent.getScreenY() + dragDelta.y);
layout.getChildren().set(
0,
makeSmoke(stage)
);
inDrag.set(true);
});
byNode.setOnMouseEntered(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
byNode.setCursor(Cursor.HAND);
}
});
byNode.setOnMouseExited(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
byNode.setCursor(Cursor.DEFAULT);
}
});
}
private javafx.scene.shape.Rectangle makeSmoke(Stage stage) {
return new javafx.scene.shape.Rectangle(
stage.getWidth(),
stage.getHeight(),
Color.WHITESMOKE.deriveColor(
0, 1, 1, 0.08
)
);
}
/** records relative x and y co-ordinates. */
private static class Delta {
double x, y;
}
public static void main(String[] args) {
launch(args);
}
}
Related Questions
Frosted Glass Effect in JavaFX?
How do I create a JavaFX transparent stage with shadows on only the border?
The visual effect that you want for OS dependent window decoration, can only be achieved through the APIs that OS provides. And thus was eliminated by StageStyle.TRANSPARENT below.
For JavaFX content itself, you can control the visuals of the stage > scene > root pane hierarchy. Stage and scene do not (and not aimed to) support advanced stylings so were eliminated by setting as transparent below.
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.setStyle("-fx-background-color: null;");
root.setPadding(new Insets(10));
DoubleProperty doubleProperty = new SimpleDoubleProperty(0);
Region region = new Region();
region.styleProperty().bind(Bindings
.concat("-fx-background-radius:20; -fx-background-color: rgba(56, 176, 209, ")
.concat(doubleProperty)
.concat(");"));
region.setEffect(new DropShadow(10, Color.GREY));
Slider slider = new Slider(0, 1, .3);
doubleProperty.bind(slider.valueProperty());
root.getChildren().addAll(region, slider);
primaryStage.initStyle(StageStyle.TRANSPARENT);
Scene scene = new Scene(root, 300, 250);
scene.setFill(Color.TRANSPARENT);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
However the drop shadow effect does not play well with alpha value of the background color. You can observe it by changing the shadow's color to another contrast one.
Output:
To expand on Jewlsea's answer .. And using the above example with JavaFX ONLY ..
While the classes are not public API, it does avoid the AWT stack completely.
Here is a non public example :
// copy a background node to be frozen over.
private Image copyBackground(Stage stage) {
final int X = (int) stage.getX();
final int Y = (int) stage.getY();
final int W = (int) stage.getWidth();
final int H = (int) stage.getHeight();
final Screen screen = Screen.getPrimary();
try {
Robot rbt = com.sun.glass.ui.Application.GetApplication().createRobot();
Pixels p = rbt.getScreenCapture(
(int)screen.getBounds().getMinX(),
(int)screen.getBounds().getMinY(),
(int)screen.getBounds().getWidth(),
(int)screen.getBounds().getHeight(),
true
);
WritableImage dskTop = new WritableImage((int)screen.getBounds().getWidth(), (int)screen.getBounds().getHeight());
dskTop.getPixelWriter().setPixels(
(int)screen.getBounds().getMinX(),
(int)screen.getBounds().getMinY(),
(int)screen.getBounds().getWidth(),
(int)screen.getBounds().getHeight(),
PixelFormat.getByteBgraPreInstance(),
p.asByteBuffer(),
(int)(screen.getBounds().getWidth() * 4)
);
WritableImage image = new WritableImage(W,H);
image.getPixelWriter().setPixels(0, 0, W, H, dskTop.getPixelReader(), X, Y);
return image;
} catch (Exception e) {
System.out.println("The robot of doom strikes!");
e.printStackTrace();
return null;
}
}
Results with a small dropshadow added:
DropShadow shdw = new DropShadow();
shdw.setBlurType(BlurType.GAUSSIAN);
shdw.setColor(Color.GAINSBORO);
shdw.setRadius(10);
shdw.setSpread(0.12);
shdw.setHeight(10);
shdw.setWidth(10);
layout.setEffect(shdw);
The opacity is a property of Node, which is the parent class in JavaFX for things that show up on the screen. http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#opacityProperty
So you can just set the opacity on the object that you want to have fade away. You then have to add some sort of way to change the opacity on the desired object. Using the slider from your image is one way, but there are others.
Drop shadows can be done using the DropShadow effect... http://docs.oracle.com/javafx/2/api/javafx/scene/effect/DropShadow.html. I have never used it. This is a little high level but if there are follow up questions in the comments I can help answer them.
In the java doc it says for setMouseTransparent that is affects all children as well as the parent.
How can it be made so only the parent's transparent areas (can see other nodes below it but not responding to mouse events) are transparent to mouse events so that the nodes below it may receive them.
This happens when stacking two XYCharts in the same pane. Only the last one added can receive events.
Set pickOnBounds for the relevant nodes to false, then clicking on transparent areas in a node won't register a click with that node.
Defines how the picking computation is done for this node when triggered by a MouseEvent or a contains function call. If pickOnBounds is true, then picking is computed by intersecting with the bounds of this node, else picking is computed by intersecting with the geometric shape of this node.
Sample Output
This sample is actually far more complicated than is necessary to demonstrate the pickOnBounds function - but I just did something this complicated so that it shows what happens "when stacking two XYCharts in the same pane" as mentioned in the poster's question.
In the sample below two line charts are stacked on top of each other and the mouse is moved over the data line in one chart which has a glow function attached to it's mouseenter event. The mouse is then moved off of the first line chart data and the glow is removed from it. The mouse is then placed over the second line chart data of an underlying stacked chart and the glow is added to that linechart in the underlying stacked chart.
This sample was developed using Java8 and the coloring and behaviour described is what I exeperienced running the program on Mac OS X and Java 8b91.
Sample Code
The code below is just for demonstrating that pickOnBounds does work for allowing you to pass mouse events through transparent regions stacked on top of opaque node shapes. It is not a recommended code practice to follow for styling lines in charts (you are better off using style sheets than lookups for that), it is also not necessary that you use a line chart stack to get multiple series on a single chart - it was only necessary or simpler to do these things to demonstrate the pick on bounds concept application for this answer.
Note the recursive call to set the pickOnBounds property for the charts after the charts have been shown on a stage and all of their requisite nodes created.
Sample code is an adaption of JavaFX 2 XYChart.Series and setOnMouseEntered:
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.scene.effect.Glow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class LineChartSample extends Application {
#SuppressWarnings("unchecked")
#Override public void start(Stage stage) {
// initialize data
ObservableList<XYChart.Data> data = FXCollections.observableArrayList(
new XYChart.Data(1, 23),new XYChart.Data(2, 14),new XYChart.Data(3, 15),new XYChart.Data(4, 24),new XYChart.Data(5, 34),new XYChart.Data(6, 36),new XYChart.Data(7, 22),new XYChart.Data(8, 45),new XYChart.Data(9, 43),new XYChart.Data(10, 17),new XYChart.Data(11, 29),new XYChart.Data(12, 25)
);
ObservableList<XYChart.Data> reversedData = FXCollections.observableArrayList(
new XYChart.Data(1, 25), new XYChart.Data(2, 29), new XYChart.Data(3, 17), new XYChart.Data(4, 43), new XYChart.Data(5, 45), new XYChart.Data(6, 22), new XYChart.Data(7, 36), new XYChart.Data(8, 34), new XYChart.Data(9, 24), new XYChart.Data(10, 15), new XYChart.Data(11, 14), new XYChart.Data(12, 23)
);
// create charts
final LineChart<Number, Number> lineChart = createChart(data);
final LineChart<Number, Number> reverseLineChart = createChart(reversedData);
StackPane layout = new StackPane();
layout.getChildren().setAll(
lineChart,
reverseLineChart
);
// show the scene.
Scene scene = new Scene(layout, 800, 600);
stage.setScene(scene);
stage.show();
// make one line chart line green so it is easy to see which is which.
reverseLineChart.lookup(".default-color0.chart-series-line").setStyle("-fx-stroke: forestgreen;");
// turn off pick on bounds for the charts so that clicks only register when you click on shapes.
turnOffPickOnBoundsFor(lineChart);
turnOffPickOnBoundsFor(reverseLineChart);
// add a glow when you mouse over the lines in the line chart so that you can see that they are chosen.
addGlowOnMouseOverData(lineChart);
addGlowOnMouseOverData(reverseLineChart);
}
#SuppressWarnings("unchecked")
private void turnOffPickOnBoundsFor(Node n) {
n.setPickOnBounds(false);
if (n instanceof Parent) {
for (Node c: ((Parent) n).getChildrenUnmodifiable()) {
turnOffPickOnBoundsFor(c);
}
}
}
private void addGlowOnMouseOverData(LineChart<Number, Number> lineChart) {
// make the first series in the chart glow when you mouse over it.
Node n = lineChart.lookup(".chart-series-line.series0");
if (n != null && n instanceof Path) {
final Path path = (Path) n;
final Glow glow = new Glow(.8);
path.setEffect(null);
path.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
path.setEffect(glow);
}
});
path.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
path.setEffect(null);
}
});
}
}
private LineChart<Number, Number> createChart(ObservableList<XYChart.Data> data) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
XYChart.Series series = new XYChart.Series(data);
series.setName("My portfolio");
series.getData().addAll();
lineChart.getData().add(series);
lineChart.setCreateSymbols(false);
lineChart.setLegendVisible(false);
return lineChart;
}
public static void main(String[] args) { launch(args); }
}
Instead of doing this:
// turn off pick on bounds for the charts so that clicks only register when you click on shapes.
turnOffPickOnBoundsFor(lineChart);
turnOffPickOnBoundsFor(reverseLineChart);
do this:
// turn off pick on bounds for the charts so that clicks only register when you click on shapes.
turnOffPickOnBoundsFor(reverseLineChart, false);
with the folling methods.
private boolean turnOffPickOnBoundsFor(Node n, boolean plotContent) {
boolean result = false;
boolean plotContentFound = false;
n.setPickOnBounds(false);
if(!plotContent){
if(containsStyle(n)){
plotContentFound = true;
result=true;
}
if (n instanceof Parent) {
for (Node c : ((Parent) n).getChildrenUnmodifiable()) {
if(turnOffPickOnBoundsFor(c,plotContentFound)){
result = true;
}
}
}
n.setMouseTransparent(!result);
}
return result;
}
private boolean containsStyle(Node node){
boolean result = false;
for (String object : node.getStyleClass()) {
if(object.equals("plot-content")){
result = true;
break;
}
}
return result;
}
Also you will need to make the chart in front(reverseLineChart) transparent.
The code posted in jewelsea answer does not work. To make it work I implemented the changes proposed is user1638436 answer and Julia Grabovska comment. Here is a working version for the sake of future readers:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.effect.Glow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class LineChartSample extends Application {
#SuppressWarnings("unchecked")
#Override public void start(Stage stage) {
// initialize data
ObservableList<XYChart.Data> data = FXCollections.observableArrayList(
new XYChart.Data(1, 23),new XYChart.Data(2, 14),new XYChart.Data(3, 15),new XYChart.Data(4, 24),new XYChart.Data(5, 34),new XYChart.Data(6, 36),new XYChart.Data(7, 22),new XYChart.Data(8, 45),new XYChart.Data(9, 43),new XYChart.Data(10, 17),new XYChart.Data(11, 29),new XYChart.Data(12, 25)
);
ObservableList<XYChart.Data> reversedData = FXCollections.observableArrayList(
new XYChart.Data(1, 25), new XYChart.Data(2, 29), new XYChart.Data(3, 17), new XYChart.Data(4, 43), new XYChart.Data(5, 45), new XYChart.Data(6, 22), new XYChart.Data(7, 36), new XYChart.Data(8, 34), new XYChart.Data(9, 24), new XYChart.Data(10, 15), new XYChart.Data(11, 14), new XYChart.Data(12, 23)
);
// create charts
final LineChart<Number, Number> bottomLineChart = createChart(data);
final LineChart<Number, Number> topLineChart = createChart(reversedData);
//add css to make top chart line transparent as pointed out by Julia Grabovska
//and user1638436, as well as make line green
topLineChart.getStylesheets().add(getClass().getResource("LineChartSample.css").toExternalForm());
StackPane layout = new StackPane(bottomLineChart, topLineChart);
// show the scene.
Scene scene = new Scene(layout, 800, 600);
stage.setScene(scene);
stage.show();
// turn off pick on bounds for the charts so that clicks only register when you click on shapes.
turnOffPickOnBoundsFor(topLineChart, false); //taken from user1638436 answer
// add a glow when you mouse over the lines in the line chart so that you can see that they are chosen.
addGlowOnMouseOverData(bottomLineChart);
addGlowOnMouseOverData(topLineChart);
}
//taken from user1638436 answer (https://stackoverflow.com/a/18104172/3992939)
private boolean turnOffPickOnBoundsFor(Node n, boolean plotContent) {
boolean result = false;
boolean plotContentFound = false;
n.setPickOnBounds(false);
if(!plotContent){
if(containsPlotContent(n)){
plotContentFound = true;
result=true;
}
if (n instanceof Parent) {
for (Node c : ((Parent) n).getChildrenUnmodifiable()) {
if(turnOffPickOnBoundsFor(c,plotContentFound)){
result = true;
}
}
}
n.setMouseTransparent(!result);
}
return result;
}
private boolean containsPlotContent(Node node){
boolean result = false;
for (String object : node.getStyleClass()) {
if(object.equals("plot-content")){
result = true;
break;
}
}
return result;
}
private void addGlowOnMouseOverData(LineChart<Number, Number> lineChart) {
// make the first series in the chart glow when you mouse over it.
Node n = lineChart.lookup(".chart-series-line.series0");
if ((n != null) && (n instanceof Path)) {
final Path path = (Path) n;
final Glow glow = new Glow(.8);
path.setEffect(null);
path.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
path.setEffect(glow);
}
});
path.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
path.setEffect(null);
}
});
}
}
private LineChart<Number, Number> createChart(ObservableList<XYChart.Data> data) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle("Stock Monitoring, 2010");
XYChart.Series series = new XYChart.Series(data);
series.setName("My portfolio");
series.getData().addAll();
lineChart.getData().add(series);
lineChart.setCreateSymbols(false);
lineChart.setLegendVisible(false);
return lineChart;
}
public static void main(String[] args) { launch(args); }
}
LineChartSample.css:
.chart-plot-background {
-fx-background-color:transparent;
}
.default-color0.chart-series-line{
-fx-stroke: forestgreen;
}
A simpler version of turnOffPickOnBoundsFor method:
private boolean turnOffPickOnBoundsFor(Node n) {
n.setPickOnBounds(false);
boolean isContainPlotContent = containsPlotContent(n);
if (! isContainPlotContent && (n instanceof Parent) ) {
for (Node c : ((Parent) n).getChildrenUnmodifiable()) {
if(turnOffPickOnBoundsFor(c)){
isContainPlotContent = true;
}
}
}
n.setMouseTransparent(!isContainPlotContent);
return isContainPlotContent;
}
Based on jewelsea answer setting top pane background color of the pane to null and topPane.setPickOnBounds(false); works fine:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class PropagateEvents extends Application {
private double x, y;
#Override public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane(getBottomPane(), getTopPane());
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private Pane getBottomPane() {
Pane pane = new Pane();
pane.setStyle("-fx-background-color : yellow;");
pane.setPrefSize(250,200);
pane.setOnMouseClicked(e-> System.out.println("Bottom pane recieved click event"));
return pane;
}
private Pane getTopPane() {
Label label = new Label();
label.setPrefSize(20,10);
label.setStyle("-fx-background-color:red;");
label.layoutXProperty().setValue(30); label.layoutYProperty().setValue(30);
addDragSupport(label);
Pane pane = new Pane(label);
// NULL color setPickOnBounds do the trick
pane.setPickOnBounds(false);
pane.setStyle("-fx-background-color: null; ");
return pane;
}
//drag support for red label
private void addDragSupport(Node node) {
node.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
x = node.getLayoutX() - mouseEvent.getSceneX();
y = node.getLayoutY() - mouseEvent.getSceneY();
node.setCursor(Cursor.MOVE);
}
});
node.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
node.setCursor(Cursor.HAND);
}
});
node.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
node.setLayoutX(mouseEvent.getSceneX() + x);
node.setLayoutY(mouseEvent.getSceneY() + y);
}
});
node.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
node.setCursor(Cursor.HAND);
}
});
}
public static void main (String[] args) {launch(null); }
}