JavaFX detect when scrollbar movement is complete - javafx

Swing provided java.awt.event.AdjustmentEvent.getValueIsAdjusting() which could be used to detect when a JScrollBar movement was completed. This was used in a legacy application so that it only requested data once rather than on every little scroll movement.
JFrame frame = new JFrame();
JScrollBar comp = new JScrollBar();
comp.addAdjustmentListener(e -> {
if (!e.getValueIsAdjusting()) {
System.out.println("Finished " + e.getValue());
}
});
frame.add(comp);
comp.setPreferredSize(new Dimension(25, 300));
frame.pack();
frame.setVisible(true);
I'm looking for an equivalent method in JavaFX with ScrollBar. It appears that ScrollBar only has a way of monitoring the current value. I've reviewed the code in com.sun.javafx.scene.control.behavior.ScrollBarBehavior but can see no obvious way to do this.
My workaround for now is fairly ugly. It uses a pair of event listeners to drag if the mouse is pressed, this state is used to maintain another valueProperty() which is only updated when the mouse is released.
Is there a better/easier way to do this?
#Override
public void start(Stage primaryStage) throws Exception {
ScrollBar scrollbar = new ScrollBar();
new ScrollBarFinishedAdjusting(scrollbar).valueProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("Finished " + newValue);
});
primaryStage.setScene(new Scene(scrollbar));
primaryStage.show();
}
private class ScrollBarFinishedAdjusting {
private boolean mouseDown;
private DoubleProperty value = new SimpleDoubleProperty();
private ScrollBar scrollbar;
public ScrollBarFinishedAdjusting(ScrollBar scrollbar) {
this.scrollbar = scrollbar;
scrollbar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!mouseDown) {
update();
}
});
scrollbar.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
mouseDown = true;
});
scrollbar.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
mouseDown = false;
update();
});
update();
}
private void update() {
value.set(scrollbar.getValue());
}
public ReadOnlyDoubleProperty valueProperty() {
return value;
}
}

Related

JavaFX button unselect

I'm trying to make a sprite editor using JavaFX using buttons as the pixels. I am able to change the colour of each button on press, but I'm trying to get it so if I click and drag I can paint multiple pixels.
The problem I'm finding is that after clicking and holding onto a button, I am unable to select the new button when I move the mouse over new button to select that one too. If I click and drag re-entering that button I can get the "Paint Dragged Pixel" debug message, but not if I enter a new pixel with the mouse down, which is what I want. Also I can get the pixel button to print "Entered Pixel" when the mouse enters any button, but not for when I click and drag to a new pixel.
I think the problem is that when I click on one pixel I am locked in, and unable to select a new pixel by hovering over a new one. Is there a way to unbind this selection, or is the problem a different one.
Main Application:
public class Main extends Application {
boolean mousePressed = false;
public boolean isMousePressed() {
return mousePressed;
}
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane borderPane = new BorderPane();
primaryStage.setTitle("SpriteSheet");
Group root = new Group();
Scene scene = new Scene(borderPane, 500,200);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GridPane gridPane = new GridPane();
borderPane.setCenter(root);
for(int x = 0; x < 10; x++)
{
for(int y = 0; y < 10; y++) {
PixelButton button = new PixelButton();
button.setParentMain(this);
button.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePressed = true;
System.out.println("mouseDown");
}
});
button.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePressed = false;
System.out.println("mouseUp");
}
});
gridPane.add(button, x, y);
}
}
root.getChildren().add(gridPane);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The class for the PixelButton.
public class PixelButton extends Button {
Main parentMain;
public void setParentMain(Main parent) {
parentMain = parent;
}
public PixelButton() {
this.setMinSize(10, 10);
this.setPrefSize(10, 10);
this.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
}
});
this.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("Entered Pixel");
if(parentMain.isMousePressed()){
System.out.println("Paint Dragged Pixel");
}
}
});
}
}
Thank you in advance.
Ok so I have been thinking about this one for a bit and have finally come up with a solution I simplified my solution a bit and used rectangles instead of buttons but you can transfer most of the code to the buttons as well so to start with this is not the exact functionality you were looking for but as close as I could get basically I fire an event on mouse press that releases the mouse click and as long as that event is not coming from the rectangle then dont flip the painting boolean paint and so you basically click to enter a "Paint Mode" and click again to get out of coloring tiles
public class Main extends Application {
private boolean mousePressed;
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane borderPane = new BorderPane();
primaryStage.setTitle("SpriteSheet");
Group root = new Group();
Scene scene = new Scene(borderPane, 500,200);
// scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GridPane gridPane = new GridPane();
borderPane.setCenter(root);
for(int x = 0; x < 10; x++) {
for(int y = 0; y < 10; y++) {
Rectangle rectangle = new Rectangle(10, 10);
rectangle.setOnMousePressed(event -> {
mousePressed = true;
System.out.println("mouseDown");
rectangle.fireEvent(new MouseEvent(MouseEvent.MOUSE_RELEASED,
rectangle.getLayoutX(), rectangle.getLayoutY(), rectangle.getLayoutX(), rectangle.getLayoutY(),
MouseButton.PRIMARY, 1,
false, false, false, false,
false, false, false, false,
false, false, null));
});
rectangle.setOnMouseReleased(event -> {
System.out.println(event.getSource());
if(!event.getSource().toString().equals("Rectangle[x=0.0, y=0.0, width=10.0, height=10.0, fill=0x000000ff]")) {
mousePressed = false;
System.out.println("mouseUp");
}
});
rectangle.setOnMouseMoved(event -> {
if(mousePressed) {
rectangle.setFill(Color.BLUE);
}
});
gridPane.add(rectangle, x, y);
}
}
root.getChildren().add(gridPane);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}

JavaFX Spinner change is slow with click and hold of mouse button

The speed of Spinner update is slow when I click and hold the up/down arrow buttons. Is there a way to increase the change speed?
When I click, click, click with the mouse, the spinner values change as fast as I click. It also changes fast if I use the up/down arrows on the keyboard for each key press or if I hold down the up/down arrow keys. I want the values to change that fast when I click and hold on the arrow buttons.
Anyone know a way to do that?
The SpinnerBehavior of the SpinnerSkin triggers updates every 750 ms. Unfortunately there is no way to simply set/modify this behavour without using reflection to access private members. Therefore the only way to do this without reflection is using event filters to trigger the updates at a faster rate:
private static final PseudoClass PRESSED = PseudoClass.getPseudoClass("pressed");
#Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner(Integer.MIN_VALUE, Integer.MAX_VALUE, 0);
class IncrementHandler implements EventHandler<MouseEvent> {
private Spinner spinner;
private boolean increment;
private long startTimestamp;
private static final long DELAY = 1000l * 1000L * 750L; // 0.75 sec
private Node button;
private final AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
if (now - startTimestamp >= DELAY) {
// trigger updates every frame once the initial delay is over
if (increment) {
spinner.increment();
} else {
spinner.decrement();
}
}
}
};
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
Spinner source = (Spinner) event.getSource();
Node node = event.getPickResult().getIntersectedNode();
Boolean increment = null;
// find which kind of button was pressed and if one was pressed
while (increment == null && node != source) {
if (node.getStyleClass().contains("increment-arrow-button")) {
increment = Boolean.TRUE;
} else if (node.getStyleClass().contains("decrement-arrow-button")) {
increment = Boolean.FALSE;
} else {
node = node.getParent();
}
}
if (increment != null) {
event.consume();
source.requestFocus();
spinner = source;
this.increment = increment;
// timestamp to calculate the delay
startTimestamp = System.nanoTime();
button = node;
// update for css styling
node.pseudoClassStateChanged(PRESSED, true);
// first value update
timer.handle(startTimestamp + DELAY);
// trigger timer for more updates later
timer.start();
}
}
}
public void stop() {
timer.stop();
button.pseudoClassStateChanged(PRESSED, false);
button = null;
spinner = null;
}
}
IncrementHandler handler = new IncrementHandler();
spinner.addEventFilter(MouseEvent.MOUSE_PRESSED, handler);
spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
if (evt.getButton() == MouseButton.PRIMARY) {
handler.stop();
}
});
Scene scene = new Scene(spinner);
primaryStage.setScene(scene);
primaryStage.show();
}
I modified the answer of fabian a little bit to decrease the speed of the spinner while holding mouse down:
private int currentFrame = 0;
private int previousFrame = 0;
#Override
public void handle(long now)
{
if (now - startTimestamp >= initialDelay)
{
// Single or holded mouse click
if (currentFrame == previousFrame || currentFrame % 10 == 0)
{
if (increment)
{
spinner.increment();
}
else
{
spinner.decrement();
}
}
}
++currentFrame;
}
And after stopping the timer we adjust previousFrame again:
public void stop()
{
previousFrame = currentFrame;
[...]
}
A small improvement to Fabian's answer. Making the following mod to the MOUSE_RELEASED addEventerFilter will stop a NullPointerException caused when clicking the textfield associated with the spinner. Cheers Fabian!
spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
Node node = evt.getPickResult().getIntersectedNode();
if (node.getStyleClass().contains("increment-arrow-button") ||
node.getStyleClass().contains("decrement-arrow-button")) {
if (evt.getButton() == MouseButton.PRIMARY) {
handler.stop();
}
}
});
An alternative to changing the update speed might in some cases be adjusting the amount by which the value increments/decrements per update.
SpinnerValueFactory.IntegerSpinnerValueFactory intFactory =
(SpinnerValueFactory.IntegerSpinnerValueFactory) spinner.getValueFactory();
intFactory.setAmountToStepBy(100);
Reference: http://news.kynosarges.org/2016/10/28/javafx-spinner-for-numbers/

JavaFX WhatApp-Like ConversationView

I'm trying to make a WhatsApp-Like Conversation-View in JavaFX.
In order to make the sent messages appear on the right and the received messages appear on the left then I cannot use TextArea. How can I do it? I tried GridPane without TextArea but it didn't make things easier.
Moreover, is it a good practice to make controls static?
Extra: if you can also help me do the chat bubble behind the text, it would be great.
Here is my code:
public class ConversationView implements WhatAppView {
private static Label nameLabel, statusLabel;
private static TextField messageTextField;
static TextArea messagesTextArea;
private static GridPane conversationSection;
private static Label changeViewLink;
private static Button sendMsgButton;
// private static int rowIndex = 1;
public void showView() {
AppMain.stage.setResizable(false);
AppMain.stage.setWidth(350);
AppMain.stage.setHeight(550);
BorderPane rootPane = new BorderPane();
rootPane.setPadding(new Insets(5, 5, 5, 5));
final int sectionHeight = 55;
StackPane contactSection = new StackPane();
nameLabel = new Label("RW");
statusLabel = new Label("Online");
changeViewLink = new Label("Go Back");
changeViewLink.setStyle("-fx-text-fill: blue;");
changeViewLink.styleProperty().bind(
Bindings.when(changeViewLink.hoverProperty())
.then(new SimpleStringProperty("-fx-underline: true; -fx-text-fill: blue;"))
.otherwise(new SimpleStringProperty("-fx-underline: false; -fx-text-fill: blue;")));
changeViewLink.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
AppMain.changeView(new ChatsView());
}
});
contactSection.getChildren().addAll(nameLabel, statusLabel, changeViewLink);
StackPane.setAlignment(changeViewLink, Pos.TOP_RIGHT);
StackPane.setAlignment(statusLabel, Pos.BOTTOM_CENTER);
contactSection.setPrefHeight(sectionHeight);
conversationSection = new GridPane();
conversationSection.setStyle("-fx-background-image: url('whatsapp-wallpaper.jpg')");
messagesTextArea = new TextArea();
messagesTextArea.setEditable(false);
// conversationSection.getColumnConstraints().addAll(new
// ColumnConstraints(AppMain.stage.getWidth()/2 - 10), new
// ColumnConstraints(AppMain.stage.getWidth()/2 - 10));
conversationSection.add(messagesTextArea, 0, 0);
conversationSection.setPrefSize(AppMain.stage.getWidth(), AppMain.stage.getHeight());
// conversationSection.getStylesheets().add("conversation.css");
ScrollPane scroll = new ScrollPane();
scroll.setPrefSize(conversationSection.getWidth(), conversationSection.getHeight());
scroll.setContent(conversationSection);
FlowPane messageSection = new FlowPane();
sendMsgButton = new Button("_Send");
sendMsgButton.setDisable(true);
sendMsgButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
sendMsg();
}
});
sendMsgButton.setPrefHeight(sectionHeight);
Tooltip sendMsgToolTip = new Tooltip("Send Message");
Tooltip.install(sendMsgButton, sendMsgToolTip);
FlowPane.setMargin(sendMsgButton, new Insets(0, 0, 0, 5));
messageTextField = new TextField();
messageTextField.setPromptText("Type your message here...");
Platform.runLater(new Runnable() { // 100% focus
public void run() {
messageTextField.requestFocus();
}
});
messageTextField.setPrefWidth(AppMain.stage.getWidth() - AppMain.stage.getWidth() / 5);
messageTextField.setPrefHeight(sectionHeight);
messageTextField.setAlignment(Pos.TOP_LEFT);
messageTextField.setOnKeyTyped(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (messageTextField.getText() != null && !messageTextField.getText().isEmpty()) {
sendMsgButton.setDisable(false);
} else {
sendMsgButton.setDisable(true);
}
}
});
messageTextField.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode().equals(KeyCode.ENTER) && messageTextField.getText() != null
&& !messageTextField.getText().isEmpty()) {
sendMsg();
}
}
});
messageSection.getChildren().add(messageTextField);
messageSection.getChildren().add(sendMsgButton);
messageSection.setPrefHeight(sectionHeight);
rootPane.setTop(contactSection);
rootPane.setCenter(conversationSection);
rootPane.setBottom(messageSection);
Scene scene = new Scene(rootPane);
AppMain.stage.setScene(scene);
AppMain.stage.setTitle("WhatsApp");
}
}
public class AppMain extends Application {
static Stage stage;
#Override
public void start(Stage primaryStage) throws Exception {
stage = primaryStage;
AppMain.stage.show();
changeView(new ConversationView());
}
public static void changeView(WhatAppView view) {
view.showView();
}
}
public interface WhatAppView {
public void showView();
}
You can create a custom control to determine message alignment and aesthetics such as the bubble like appearance. As a fan of HBox and VBox, I would recommend their usage in combination with an SVGPath to decorate the message.
SVGPath's let you draw custom shapes by providing information on the lines, arcs etc. These aren't unique to java so there are a few resources available to see some basic/advanced examples. My recommendation would be to read here: SVGPath and use the TryitEditor to experiment
Here are two quick examples:
When it comes to laying out the messages a VBox would suffice. You can bind the viewable children to an ObservableList of messages you would be able to iterate later. The added benefit of this is that adding to the list will update the UI automatically, and you'll also be able to iterate these later in the event you implement additional features such as delete, forward etc
I'd recommend reading up on the Bindings api, particularly bindContentBidirectional for more information on this
Using my above recommendations i've written a small example below you can reference. It's not visually impressive, but hopefully you can get some ideas from it, particularly this:
Extra: if you can also help me do the chat bubble behind the text, it
would be great.
The messages/speech bubbles:
enum SpeechDirection{
LEFT, RIGHT
}
public class SpeechBox extends HBox{
private Color DEFAULT_SENDER_COLOR = Color.GOLD;
private Color DEFAULT_RECEIVER_COLOR = Color.LIMEGREEN;
private Background DEFAULT_SENDER_BACKGROUND, DEFAULT_RECEIVER_BACKGROUND;
private String message;
private SpeechDirection direction;
private Label displayedText;
private SVGPath directionIndicator;
public SpeechBox(String message, SpeechDirection direction){
this.message = message;
this.direction = direction;
initialiseDefaults();
setupElements();
}
private void initialiseDefaults(){
DEFAULT_SENDER_BACKGROUND = new Background(
new BackgroundFill(DEFAULT_SENDER_COLOR, new CornerRadii(5,0,5,5,false), Insets.EMPTY));
DEFAULT_RECEIVER_BACKGROUND = new Background(
new BackgroundFill(DEFAULT_RECEIVER_COLOR, new CornerRadii(0,5,5,5,false), Insets.EMPTY));
}
private void setupElements(){
displayedText = new Label(message);
displayedText.setPadding(new Insets(5));
displayedText.setWrapText(true);
directionIndicator = new SVGPath();
if(direction == SpeechDirection.LEFT){
configureForReceiver();
}
else{
configureForSender();
}
}
private void configureForSender(){
displayedText.setBackground(DEFAULT_SENDER_BACKGROUND);
displayedText.setAlignment(Pos.CENTER_RIGHT);
directionIndicator.setContent("M10 0 L0 10 L0 0 Z");
directionIndicator.setFill(DEFAULT_SENDER_COLOR);
HBox container = new HBox(displayedText, directionIndicator);
//Use at most 75% of the width provided to the SpeechBox for displaying the message
container.maxWidthProperty().bind(widthProperty().multiply(0.75));
getChildren().setAll(container);
setAlignment(Pos.CENTER_RIGHT);
}
private void configureForReceiver(){
displayedText.setBackground(DEFAULT_RECEIVER_BACKGROUND);
displayedText.setAlignment(Pos.CENTER_LEFT);
directionIndicator.setContent("M0 0 L10 0 L10 10 Z");
directionIndicator.setFill(DEFAULT_RECEIVER_COLOR);
HBox container = new HBox(directionIndicator, displayedText);
//Use at most 75% of the width provided to the SpeechBox for displaying the message
container.maxWidthProperty().bind(widthProperty().multiply(0.75));
getChildren().setAll(container);
setAlignment(Pos.CENTER_LEFT);
}
}
Conversation window:
public class ConversationView extends VBox{
private String conversationPartner;
private ObservableList<Node> speechBubbles = FXCollections.observableArrayList();
private Label contactHeader;
private ScrollPane messageScroller;
private VBox messageContainer;
private HBox inputContainer;
public ConversationView(String conversationPartner){
super(5);
this.conversationPartner = conversationPartner;
setupElements();
}
private void setupElements(){
setupContactHeader();
setupMessageDisplay();
setupInputDisplay();
getChildren().setAll(contactHeader, messageScroller, inputContainer);
setPadding(new Insets(5));
}
private void setupContactHeader(){
contactHeader = new Label(conversationPartner);
contactHeader.setAlignment(Pos.CENTER);
contactHeader.setFont(Font.font("Comic Sans MS", 14));
}
private void setupMessageDisplay(){
messageContainer = new VBox(5);
Bindings.bindContentBidirectional(speechBubbles, messageContainer.getChildren());
messageScroller = new ScrollPane(messageContainer);
messageScroller.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
messageScroller.setHbarPolicy(ScrollBarPolicy.NEVER);
messageScroller.setPrefHeight(300);
messageScroller.prefWidthProperty().bind(messageContainer.prefWidthProperty().subtract(5));
messageScroller.setFitToWidth(true);
//Make the scroller scroll to the bottom when a new message is added
speechBubbles.addListener((ListChangeListener<Node>) change -> {
while (change.next()) {
if(change.wasAdded()){
messageScroller.setVvalue(messageScroller.getVmax());
}
}
});
}
private void setupInputDisplay(){
inputContainer = new HBox(5);
TextField userInput = new TextField();
userInput.setPromptText("Enter message");
Button sendMessageButton = new Button("Send");
sendMessageButton.disableProperty().bind(userInput.lengthProperty().isEqualTo(0));
sendMessageButton.setOnAction(event-> {
sendMessage(userInput.getText());
userInput.setText("");
});
//For testing purposes
Button receiveMessageButton = new Button("Receive");
receiveMessageButton.disableProperty().bind(userInput.lengthProperty().isEqualTo(0));
receiveMessageButton.setOnAction(event-> {
receiveMessage(userInput.getText());
userInput.setText("");
});
inputContainer.getChildren().setAll(userInput, sendMessageButton, receiveMessageButton);
}
public void sendMessage(String message){
speechBubbles.add(new SpeechBox(message, SpeechDirection.RIGHT));
}
public void receiveMessage(String message){
speechBubbles.add(new SpeechBox(message, SpeechDirection.LEFT));
}
}
Output:

Children's hover listening with JavaFX

Is it possible to add .hoverProperty().addListener to all children(in my case buttons) of HBox? I know that I can assign separate listeners for each button. But I was interested if it is possible to assign one listener to all children at once. Buttons of HBox have 15 px spacing between them.
Just add the listener to the HBox:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Group root = new Group();
HBox hBox = new HBox();
hBox.setSpacing(30);
for (int i = 0; i < 10; i++) {
hBox.getChildren().add(new Button("Button " + i));
}
hBox.hoverProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println("Hover: " + oldValue + " -> " + newValue);
}
});
hBox.addEventFilter(MouseEvent.MOUSE_ENTERED, e -> System.out.println( e));
hBox.addEventFilter(MouseEvent.MOUSE_EXITED, e -> System.out.println( e));
hBox.addEventFilter(MouseEvent.MOUSE_MOVED, e -> {
if( e.getTarget() instanceof Button) {
System.out.println( e);
}
});
hBox.setMaxHeight(100);
root.getChildren().add( hBox);
Scene scene = new Scene( root, 800, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
According to the hoverProperty documentation you could as well use a mouse listener for now:
Note that current implementation of hover relies on mouse enter and
exit events to determine whether this Node is in the hover state; this
means that this feature is currently supported only on systems that
have a mouse. Future implementations may provide alternative means of
supporting hover.

Call event handler

I have a TabPane with test button which calls this code:
Button bt1 = new Button("Select");
bt1.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(final ActionEvent event)
{
TreeClass.getConnectedAgentsMap();
TreePane.getTreeView().getSelectionModel().clearAndSelect(3);
}
});
This code selects TreeNode into TreeView:
cell.setOnMouseClicked((MouseEvent me) ->
{
if (!cell.isEmpty())
{
/// some action
}
});
As you can see this event is triggered when mouse selects tree row.
I tried to call the tree cell action with this code:
cell.selectedProperty().addListener(new ChangeListener<Boolean>()
{
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
{
/// Some Action
}
});
But it's not the proper way to use it because several times new Tab is opened. Is there a way when I click the button to call action event?
final Point2D windowCoord = new Point2D(SCENE.getWindow().getX(), SCENE.getWindow().getY());
final Point2D sceneCoord = new Point2D(SCENE.getX(), Main.getStage().getScene().getY());
final Point2D nodeCoord = MYCONTROL.localToScene(0.0, 0.0);
final double x = Math.round(windowCoord.getX() + sceneCoord.getX() + nodeCoord.getX());
final double y = Math.round(windowCoord.getY() + sceneCoord.getY() + nodeCoord.getY());
try {
Robot robot = new Robot();
robot.mouseMove(new Double(x).intValue()+1, new Double(y).intValue());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
} catch (AWTException ex) {
ex.printStackTrace();
}
Some of the code is borrowed from this website.

Resources