Multi Pane Mouse Events - javafx

We have a requirement to have a "main Node" that when the mouse hovers inside of it another Node ("hover popup Node") appears in the Scene. When the mouse hovers out of the "main Node" then the "hover popup Node" dissapears. Also, the "main Node" has an "inner Node" within that when clicked, another Node ("click popup Node") appears in the Scene and if clicked again the "click popup Node" dissapears.
The problem we have is that, when the mouse hovers inside the "inner Node", the "hover popup Node" dissapears which we do not want. We only want the "hover popup Node" to dissapear when the mouse hovers outside the "main Node". i.e. when the mouse is inside the "inner Node", we still want the "hover popup Node" to be visible.
See sample code:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MultiPaneMouseTest extends Application {
private Rectangle hoverPopupNode;
private Rectangle clickPopupNode;
#Override
public void init() throws Exception {
super.init();
}
#Override
public void start(Stage primaryStage) {
BorderPane borderPane = new BorderPane();
Rectangle mainNode = new Rectangle(200, 100);
mainNode.setArcWidth(6);
mainNode.setArcHeight(6);
mainNode.setFill(Color.web("0x00ff00"));
mainNode.setOnMouseEntered(e -> {
System.out.println("mouse entered " + e);
hoverPopupNode.setVisible(true);
});
mainNode.setOnMouseExited(e -> {
System.out.println("mouse exited " + e);
hoverPopupNode.setVisible(false);
});
Rectangle innerNode = new Rectangle(50, 25);
innerNode.setArcWidth(6);
innerNode.setArcHeight(6);
innerNode.setFill(Color.web("0xffffff"));
innerNode.setOnMouseClicked(e -> {
System.out.println("mouse clicked " + e);
clickPopupNode.setVisible(!clickPopupNode.isVisible());
});
hoverPopupNode = new Rectangle(100, 100);
hoverPopupNode.setArcWidth(6);
hoverPopupNode.setArcHeight(6);
hoverPopupNode.setFill(Color.web("0x0000ff"));
hoverPopupNode.setVisible(false);
borderPane.setRight(hoverPopupNode);
clickPopupNode = new Rectangle(100, 100);
clickPopupNode.setArcWidth(6);
clickPopupNode.setArcHeight(6);
clickPopupNode.setFill(Color.web("0xff0000"));
clickPopupNode.setVisible(false);
borderPane.setLeft(clickPopupNode);
StackPane stackPane = new StackPane();
stackPane.getChildren().add(mainNode);
stackPane.getChildren().add(innerNode);
borderPane.setCenter(stackPane);
Scene scene = new Scene(borderPane);
primaryStage.setTitle("Sample Test");
primaryStage.setScene(scene);
primaryStage.setX(500.0);
primaryStage.setY(500.0);
primaryStage.setWidth(500.0);
primaryStage.setHeight(500.0);
primaryStage.show();
}
#Override
public void stop() throws Exception {
super.stop();
}
public static void main(String[] args) {
launch(args);
}
}

Make the mainNode a Pane (or suitable subclass) and add the innerNode to it as a child node. If there is a parent-child relationship between mainNode and childNode, then the mouse will still be considered to be hovering over mainNode when it is over innerNode. You can set the background of a Pane (or any region) to achieve the same fill and corner radius effects that you have on the Rectangle.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class MultiPaneMouseTest extends Application {
private Rectangle hoverPopupNode;
private Rectangle clickPopupNode;
#Override
public void init() throws Exception {
super.init();
}
#Override
public void start(Stage primaryStage) {
BorderPane borderPane = new BorderPane();
StackPane mainNode = new StackPane();
mainNode.setMinSize(200, 100);
mainNode.setMaxSize(200, 100);
mainNode.setBackground(new Background(new BackgroundFill(Color.web("0x00ff00"), new CornerRadii(6), Insets.EMPTY)));
// note you can replace these listeners with
// hoverPopupNode.visibleProperty().bind(mainNode.hoverProperty());
// (but obviously after the hoverPopupNode has been instantiated)
mainNode.setOnMouseEntered(e -> {
System.out.println("mouse entered " + e);
hoverPopupNode.setVisible(true);
});
mainNode.setOnMouseExited(e -> {
System.out.println("mouse exited " + e);
hoverPopupNode.setVisible(false);
});
Rectangle innerNode = new Rectangle(50, 25);
innerNode.setArcWidth(6);
innerNode.setArcHeight(6);
innerNode.setFill(Color.web("0xffffff"));
innerNode.setOnMouseClicked(e -> {
System.out.println("mouse clicked " + e);
clickPopupNode.setVisible(!clickPopupNode.isVisible());
});
mainNode.getChildren().add(innerNode);
hoverPopupNode = new Rectangle(100, 100);
hoverPopupNode.setArcWidth(6);
hoverPopupNode.setArcHeight(6);
hoverPopupNode.setFill(Color.web("0x0000ff"));
hoverPopupNode.setVisible(false);
borderPane.setRight(hoverPopupNode);
clickPopupNode = new Rectangle(100, 100);
clickPopupNode.setArcWidth(6);
clickPopupNode.setArcHeight(6);
clickPopupNode.setFill(Color.web("0xff0000"));
clickPopupNode.setVisible(false);
borderPane.setLeft(clickPopupNode);
StackPane stackPane = new StackPane();
stackPane.getChildren().add(mainNode);
borderPane.setCenter(stackPane);
Scene scene = new Scene(borderPane);
primaryStage.setTitle("Sample Test");
primaryStage.setScene(scene);
primaryStage.setX(500.0);
primaryStage.setY(500.0);
primaryStage.setWidth(500.0);
primaryStage.setHeight(500.0);
primaryStage.show();
}
#Override
public void stop() throws Exception {
super.stop();
}
public static void main(String[] args) {
launch(args);
}
}

Related

How to stop transition when checkbox is unchecked javafx

So I've made a checkbox that applies a scale transition to a rectangle when checked. But the problem is that the transition keeps going even after I uncheck the checkbox. Any ideas on how to make it stop after un-checking?
checkbox.setOnAction(e -> {
ScaleTransition scaleT = new ScaleTransition(Duration.seconds(5), rectangle);
scaleT.setAutoReverse(true);
scaleT.setCycleCount(Timeline.INDEFINITE);
scaleT.setToX(2);
scaleT.setToY(2);
scaleT.play();
});
To control the animation, you need to define the transistion(with INDEFINITE cycle count) outside the CheckBox listener/action. Then you can just play/pause the animation as you required.
Below is the quick demo:
import javafx.animation.ScaleTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ScaleTransitionDemo extends Application {
#Override
public void start(Stage stage) {
Shape rectangle = new Rectangle(50, 50, Color.BLUE);
ScaleTransition transition = new ScaleTransition(Duration.seconds(1), rectangle);
transition.setDuration(Duration.seconds(1));
transition.setAutoReverse(true);
transition.setCycleCount(Timeline.INDEFINITE);
transition.setToX(3);
transition.setToY(3);
CheckBox checkBox = new CheckBox("Animate");
checkBox.selectedProperty().addListener((obs, old, selected) -> {
if (selected) {
transition.play();
} else {
transition.pause();
}
});
StackPane pane = new StackPane(rectangle);
VBox.setVgrow(pane, Priority.ALWAYS);
VBox root = new VBox(20, checkBox, pane);
root.setPadding(new Insets(10));
Scene scene = new Scene(root, 300, 300);
stage.setScene(scene);
stage.setTitle("Scale transition");
stage.show();
}
public static void main(String[] args) {
launch();
}
}
checking whether checkbox is selected or not with .isSelected() method . In this approach , scaled node will back to xy = 1 scale if checkbox is unchecked , but it will be disabled until transition ends .You can adjust setDuration . I've changed it just for gif recording. This is a single class javafx app you can try .
App.java
public class App extends Application {
#Override
public void start(Stage stage) {
Shape rectangle = new Rectangle(50, 50, Color.BLUE);
ScaleTransition scaleT = new ScaleTransition(Duration.seconds(1), rectangle);
CheckBox checkBox = new CheckBox("scale");
checkBox.setOnAction(e -> {
if (checkBox.isSelected()) {
scaleT.setDuration(Duration.seconds(1));
scaleT.setAutoReverse(true);
scaleT.setCycleCount(Timeline.INDEFINITE);
scaleT.setToX(2);
scaleT.setToY(2);
scaleT.play();
} else {
scaleT.setDuration(scaleT.getCurrentTime());
scaleT.stop();
scaleT.setCycleCount(1);
scaleT.setToX(1);
scaleT.setToY(1);
scaleT.play();
checkBox.setDisable(true);
scaleT.setOnFinished((t) -> {
checkBox.setDisable(false);
});
}
});
var scene = new Scene(new HBox(50, rectangle, checkBox), 640, 480);
stage.setScene(scene);
stage.setTitle("scale transition");
stage.show();
}
public static void main(String[] args) {
launch();
}
}

How to set popup scene mouse transparent in javafx

How to do that in JavaFX?
The popup shows up when the mouse enters a node. When the mouse enters the showing popup, the popup obscures the mouse from the node. Then the node fire exit event. How to make the popup ignore the mouse events?
code
package sample;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 300, 275));
Label labelNode = new Label("Label Node");
labelNode.setPrefHeight(200);
labelNode.styleProperty().set("-fx-background-color: orange");
Popup popup = new Popup();
popup.getScene().getRoot().setMouseTransparent(true);
AnchorPane popContent =new AnchorPane();
popContent.styleProperty().set("-fx-background-color: red");
popContent.setPrefHeight(100);
popContent.getChildren().add(new Label("Popup content"));
popup.getContent().add(popContent);
labelNode.setOnMouseEntered(event->{
Point3D point3D = labelNode.localToScene(event.getX(), event.getY(), 0);
popup.show(primaryStage, point3D.getX()-5, point3D.getY()-5);
});
labelNode.setOnMouseExited(event->{
popup.hide();
});
root.getChildren().add(labelNode);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Please try moving the cursor in to "yellow" several times.
Solution:
Keep two boolean nodeExited and popupExited statuses. Hide popup when both are true.
package sample;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class Main extends Application {
boolean nodeExited = false;
boolean popupExited = false;
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 300, 275));
Label labelNode = new Label("Label Node");
labelNode.setPrefHeight(200);
labelNode.styleProperty().set("-fx-background-color: orange");
Popup popup = new Popup();
popup.getScene().getRoot().setMouseTransparent(true);
AnchorPane popContent = new AnchorPane();
popContent.styleProperty().set("-fx-background-color: red");
popContent.setPrefHeight(100);
popContent.getChildren().add(new Label("Popup content"));
popup.getContent().add(popContent);
popup.getScene().setOnMouseEntered(event -> {
popupExited = false;
});
popup.getScene().setOnMouseExited(event -> {
popupExited = true;
if (nodeExited)
popup.hide();
});
labelNode.setOnMouseEntered(event -> {
nodeExited = false;
Point3D point3D = labelNode.localToScene(event.getX(), event.getY(), 0);
popup.show(primaryStage, point3D.getX() - 5, point3D.getY() - 5);
});
labelNode.setOnMouseExited(event -> {
nodeExited = true;
if (popupExited)
popup.hide();
});
root.getChildren().add(labelNode);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

how to get KeyEvent handled in javafx

I have tests about KeyEvent in javafx,if I use onKeyPressed() method bound to any kind of pane,it wouldn't work.bound to scene or a button would work fine.I am wondering how can I let pane associated with KeyEvent.
To add key events handlers to pane effectively you need to request focus on pane first.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class PanesKeyEventsApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
StackPane stackPane1 = new StackPane();
stackPane1.setPrefSize(200, 200);
stackPane1.setStyle("-fx-background-color: purple;");
StackPane stackPane2 = new StackPane();
stackPane2.setPrefSize(200, 200);
stackPane2.setStyle("-fx-background-color: yellow;");
HBox hBox = new HBox(stackPane1, stackPane2);
Scene scene = new Scene(hBox);
stage.setScene(scene);
stage.show();
stackPane1.setOnMouseClicked(event -> stackPane1.requestFocus());
stackPane2.setOnMouseClicked(event -> stackPane2.requestFocus());
stackPane1.addEventHandler(KeyEvent.KEY_PRESSED, event -> System.out.println("purple key pressed " + event.getCode()));
stackPane2.addEventHandler(KeyEvent.KEY_PRESSED, event -> System.out.println("yellow key pressed " + event.getCode()));
}
}

JavaFX - StackPane with Pane as EventDispatcher

In the event dispatcher pane occurs an mouse pressed event.
The pane one should show the context menu of it's combobox when a event occurs.
That works fine if the event is only dipatched to pane one.
When the event is dipatched to pane one and pane two the context menu of pane one doesn't show up.
I suppose it has something to do with the event tail and event consuming.
Until now i doesn't had a look at the EventDispatcher Class of the JDK itself.
Here is what i got so far:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
*
* #author Robert
*/
public class EventDispatcherExample extends Application {
private Group root;
private StackPane cStackPane;
private Pane cPaneEventDispatcher;
private Pane cPaneOne;
private ComboBox cComboBox;
private Pane cPaneTwo;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
root = new Group();
cStackPane = new StackPane();
cStackPane.setPrefHeight(200.0);
cStackPane.setPrefWidth(200.0);
cPaneEventDispatcher = new Pane();
cPaneEventDispatcher.setPrefHeight(200.0);
cPaneEventDispatcher.setPrefWidth(200.0);
cPaneEventDispatcher.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//System.out.println("Mouse pressed in Pane ED.");
cPaneOne.fireEvent(event);
cPaneTwo.fireEvent(event);
}
});
cPaneOne = new Pane();
cPaneOne.setPrefHeight(200.0);
cPaneOne.setPrefWidth(200.0);
cPaneOne.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//System.out.println("Mouse pressed in Pane One.");
cComboBox.show();
}
});
ObservableList<String> observableList = FXCollections.observableArrayList();
observableList.add("1");
observableList.add("2");
observableList.add("3");
cComboBox = new ComboBox();
cComboBox.setLayoutX(50.0);
cComboBox.setLayoutY(50.0);
cComboBox.setPrefHeight(30.0);
cComboBox.setPrefWidth(100.0);
cComboBox.setItems(observableList);
cPaneTwo = new Pane();
cPaneTwo.setPrefHeight(200.0);
cPaneTwo.setPrefWidth(200.0);
cPaneTwo.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//System.out.println("Mouse pressed in Pane Two.");
//Something will happen because of selected item in Combo Box of pane one...
}
});
cPaneOne.getChildren().add(cComboBox);
// add the nodes in reverse order
cStackPane.getChildren().add(cPaneTwo);
cStackPane.getChildren().add(cPaneOne);
cStackPane.getChildren().add(cPaneEventDispatcher);
root.getChildren().add(cStackPane);
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Any ideas how to handle this?
After some ideas I got a solution that at least works:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
*
* #author Robert
*/
public class EventDispatcherExample extends Application {
private Group root;
private StackPane cStackPane;
private Pane cPaneEventDispatcher;
private Pane cPaneOne;
private ComboBox cComboBox;
private boolean cComboBoxClicked = false;
private Pane cPaneTwo;
public static void main(String[] args) {
launch(args);
}
public boolean isComboBoxClicked() {
if (cComboBoxClicked == true) {
cComboBox.show();
} else {
cComboBox.hide();
}
return cComboBoxClicked;
}
#Override
public void start(Stage primaryStage) throws Exception {
root = new Group();
cStackPane = new StackPane();
cStackPane.setPrefHeight(200.0);
cStackPane.setPrefWidth(200.0);
cPaneEventDispatcher = new Pane();
cPaneEventDispatcher.setPrefHeight(200.0);
cPaneEventDispatcher.setPrefWidth(200.0);
cPaneEventDispatcher.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//System.out.println("Mouse pressed in Pane ED.");
cPaneOne.fireEvent(event);
cPaneTwo.fireEvent(event);
}
});
cPaneOne = new Pane();
cPaneOne.setPrefHeight(200.0);
cPaneOne.setPrefWidth(200.0);
cPaneOne.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//System.out.println("Mouse pressed in Pane One.");
Rectangle rect = new Rectangle(cComboBox.getLayoutX(), cComboBox.getLayoutY(),
cComboBox.getPrefWidth(), cComboBox.getPrefHeight());
cComboBoxClicked = rect.contains(event.getX(), event.getY());
}
});
ObservableList<String> observableList = FXCollections.observableArrayList();
observableList.add("1");
observableList.add("2");
observableList.add("3");
cComboBox = new ComboBox();
cComboBox.setLayoutX(50.0);
cComboBox.setLayoutY(50.0);
cComboBox.setPrefHeight(30.0);
cComboBox.setPrefWidth(100.0);
cComboBox.setItems(observableList);
cComboBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
// if cComboBoxSelectedIndex == 1 do Color on pane two
// if cComboBoxSelectedIndex == 2 do Size on pane two
// if cComboBoxSelectedIndex == 3 do ...
//System.out.println("newValue " + newValue);
}
});
cPaneTwo = new Pane();
cPaneTwo.setPrefHeight(200.0);
cPaneTwo.setPrefWidth(200.0);
cPaneTwo.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
//System.out.println("Mouse pressed in Pane Two.");
boolean cComboBoxClicked = isComboBoxClicked();
if (cComboBoxClicked){
//System.out.println("Skip code internally managed by pane two.");
return;
}
// Internal code of pane two
//...
}
});
cPaneOne.getChildren().add(cComboBox);
// add the nodes in reverse order
cStackPane.getChildren().add(cPaneTwo);
cStackPane.getChildren().add(cPaneOne);
cStackPane.getChildren().add(cPaneEventDispatcher);
root.getChildren().add(cStackPane);
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Perhaps a better approach comes up during the time of someone else.
Am looking forward...

Javafx I want to remove the dynamic element in Accordion

Picture
Question : How can I remove the element that I right clicked on? Example : World Pane2 Click And World Pane2 Remove.
Below is the code to dynamically create an element as I click the button.
Button Click:
AnchorPane newPanelContent = new AnchorPane();
VBox h = new VBox(10);
h.setPadding(new Insets(10, 0, 0, 10));
newPanelContent.getChildren().add(h);
h.getChildren().add(new Label("Hello World"));
h.getChildren().add(new Button("Click"));
TitledPane pane = new TitledPane("World Pane"+i, newPanelContent);
System.out.println(accordion);
accordion.getPanes().add(pane);
Answer found.
https://stackoverflow.com/a/42988381/6306993
pane.setOnMouseClicked(event -> {
if (MouseButton.SECONDARY.equals(event.getButton())) {
accordion.getPanes().remove(pane);
}
});
To remove a titled pane from an accordion when you right click on the titled pane:
pane.setOnMouseClicked(event -> {
if (MouseButton.SECONDARY.equals(event.getButton())) {
accordion.getPanes().remove(pane);
}
});
Where pane is a reference to a titled pane.
Full Sample App
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class PaneRemoval extends Application {
public static void main(String[] args) {
launch(args);
}
private Accordion accordion = new Accordion();
#Override
public void start(Stage stage) {
accordion.getPanes().addAll(
IntStream.range(0, 5)
.mapToObj(this::createTitledPane)
.toArray(TitledPane[]::new)
);
StackPane root = new StackPane(accordion);
root.setPadding(new Insets(10));
stage.setScene(new Scene(root));
stage.show();
}
private TitledPane createTitledPane(int i) {
Pane content = new Pane();
content.setPrefSize(100, 100);
content.setStyle("-fx-background-color: cornsilk;");
TitledPane pane = new TitledPane(i + "", content);
pane.setOnMouseClicked(event -> {
if (MouseButton.SECONDARY.equals(event.getButton())) {
accordion.getPanes().remove(pane);
}
});
return pane;
}
}

Resources