handle mouse move and click together in javafx - java

I'm trying to build a javafx app in which i need to respond to mouse movements and clicks together just like what happens in counter strike when you shoot. But the problem is when i press the mouse button it will not respond to mouse movements anymore until i release the mouse button. I want them both to work together in parallel. I tried to set my listeners in separate threads but it doesn't work.This is an image of a gun pointer.
Image image = new Image("/pointer.png"); // a 25*25 PNG icon
ImageView imageView = new ImageView(image);
scene.setCursor(Cursor.NONE);
and then :
scene.setOnMouseMoved(e -> {
imageView.setX(e.getX());
imageView.setY(e.getY());
});
scene.setOnMousePressed(e -> Toolkit.getDefaultToolkit().beep());
I also tried to put them in separate threads, it doesn't work either but if it does there is another problem, i cannot change the coordinates of a javafx component in another thread and i get this error -even if it doesn't cause an error it will not work:
java.lang.IllegalStateException: Not on FX application thread
scene.setOnMouseMoved(e -> {
Thread thread = new Thread() {
#Override
public void run() {
imageView.setX(e.getX()); // here i cannot do stuff related
imageView.setY(e.getY()); // to javafx components
}
};
thread.start();
});
scene.setOnMousePressed(e -> {
Thread thread = new Thread() {
#Override
public void run() {
Toolkit.getDefaultToolkit().beep());
}
};
thread.start();
});
I also tried this but it doesn't work either
scene.setOnMouseMoved(e -> {
imageView.setX(e.getX());
imageView.setY(e.getY());
scene.setOnMousePressed(event -> Toolkit.getDefaultToolkit().beep());
});
So how i can handle this problem, how i can respond to mouse clicks in parallel with mouse movements with no conflict.

When the mouse is clicked and held, instead of onMouseMoved use onMouseDragged with same method signature. I believe that should satisfy your requirements.
As for the exception, just for your information, in order to run code on JavaFX Application Thread simply call Platform.runLater(some Runnable code); So in your case
Thread thread = new Thread() {
#Override
public void run() {
Platform.runLater(() -> {
imageView.setX(e.getX()); // this will now run fine
imageView.setY(e.getY());
});
}
};
Nevertheless, there is absolutely no need for extra threads, since the capture of events will be propagated only to the JavaFX Application Thread. There are various ways of filtering or handling those events. More information about events can be found here

Related

Java FX : Unable to open popup from different FX thread

Hi I have application which runs both on GUI(Java FX) as well as command line.
When run as GUI, i show the status on text area. This works fine.
But the issue is when ever i try to show a error(via popup) from some different (non javafx) class, it shows me Java Fx - thread Exception not on FX thread.
Below is my code
This is my Java FX class where I wish to show popup.
public class DeploymentProcesController implements Initializable {
#FXML
private TextArea statusTextArea;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void updateGUIMessage(String message) {
if (Platform.isFxApplicationThread()) {
statusTextArea.appendText(message);
} else {
Platform.runLater(new Runnable() {
#Override
public void run() {
statusTextArea.appendText(message);
}
});
}
}
public void displayAlertMessages(final String message) {
Platform.setImplicitExit(false);
Task<Void> task = new Task<Void>() {
#Override public Void call() {
Platform.runLater(new Runnable() {
public void run() {
Alert alert = new Alert(AlertType.INFORMATION, message, ButtonType.OK);
alert.showAndWait();
}
});
return null;
}
};
new Thread(task).start();
}
}
I have a non FX class which is the entry point. So Based on type of run (command line / GUI ) I update the status.
Here is how I am calling to update the status.
public void promptUser(String message,boolean isCommandLineRun){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
if(isCommandLineRun) {
System.out.println(simpleDateFormat.format(new Date()) + " - " + message);
} else {
controller.displayAlertMessages(message);
}
}
I have no issues when i call the updateGUIMessage method from non fx class. This is because the statusArea element is on FX thread(member of this fx class).
Also I have no issues to generate a alert box on some button click,
but to display an Alert box from a different class- I am having issues since as soon as I try to generate a alert box , the application crashes, saying not on fx thread.
I understand that the Alert box is a popup and therefore may be unhandled. But can anyone help me, I want to show user a alert box, from different class.
Assuming that you want some long running code to run before the popup is called,
There are two steps that need to be done when dealing with the Fx Buttons. The first thread lets the code run as a thread when the button is pushed. The second is the Platform.runlater() which is telling the Fx Platform to execute the code in that thread from the Platform. Note that the popup will not get executed until the runlater is reached in the outer thread.
Otherwise you can call the popup directly.
public void MyExampleBtn() {
Thread t = new Thread() {
// long running work to be done, threaded so as not to hang the main controls.
// ....
// work is done do the success popup
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Success!");
alert.setHeaderText(null);
alert.setContentText("This is my popup");
alert.showAndWait();
}
});
}
};
}
Everything in the UI has to be executed from the UI application thread. That is exactly what the error message means.
Fortunately you can simply wrap your call so that it is executed in the UI thread:
if(isCommandLineRun) {
System.out.println(simpleDateFormat.format(new Date()) + " - " + message);
} else {
Platform.runLater(() -> controller.displayAlertMessages(message));
}
Finally found the solution to it,
Since Java fx runs on single thread, everything has to be on same thread. For every task (such as popup) where there needs background to pause, we need to use FutureTask.
I found this article, here :
JavaFX2: Can I pause a background Task / Service?

JavaFX & GUI-Threads: Change button text from Thread

I have a GUI with a TextArea and a Save Button. When I press the latter the text is saved. This takes around 2 seconds. During the process of saving the buttons should get another caption than before and after saving.
Here is my code:
saveButton.setText("Saving...");
Util.print("Saving...");
Thread saveIt = new Thread(new Runnable() {
#Override public void run() {
Platform.runLater(() -> {
try {
Thread.sleep(2000);
} catch (Exception ex) {
Util.print(ex);
}
saveButton.setText("Saved!");
Util.print("Saved!");
});
}
});
saveIt.setDaemon(true);
saveIt.start();
What happens:
The following output is produced on the command line after pressing the button:
Saving...
Saved!
The command line prints "Saving..." directly after I click on saveButton. 2 seconds after pressing saveButton the command line prints "Saved!" and the button caption changes to "Saved!".
What I would expect:
The command line output and the button caption show "Saving..." directly after I click on the save button. After 2 seconds the caption changes to "Saved!".
How can I achieve the expected behaviour?
Thank you very much in advance for your help.
P.S.: I know so many people have had problems with changing GUI elements from Threads. I already read some articles on StackOverflow & the web about it, but this one's a too hard nut for me. Just for reference, here is some of the things I tried so far, among others Tasks:
Constantly Update UI in Java FX worker thread
Why am I getting java.lang.IllegalStateException "Not on FX application thread" on JavaFX?
javafx, update ui from another thread
http://blog.axxg.de/javafx-ui-thread-update/
I had to put the Thread.sleep() part out of the Platform.runLater() process. Seemingly runLater() must as few workload as possible.
Platform.runLater(() -> {
saveButton.setText("Saving...");
Util.print("Saving...");
});
Thread saveIt = new Thread(new Runnable() {
#Override public void run() {
try {
sleep(2000);
} catch (Exception ex) {
Util.print(ex);
}
Platform.runLater(() -> {
saveButton.setText("Saved!");
Util.print("Saved!");
});
}
});
saveIt.setDaemon(true);
saveIt.start();
try wrapping your first setText into a Platform.runLater like this:
Platform.runLater(() -> {
saveButton.setText("Saving...");
});
Every change made to a JavaFX UI component has to been called from the JavaFX thread using the Platform.runLater

Understanding how updating GUI in thread works

Can I be explained how updating GUI threads really work? It is all messy to me.
For instance I want to update a ProgressBar in a loop. I got it that I need to put the loop into a new Task. I binded the progressBar's progressProperty to the Task's progress.
If I call the Task with
new Thread(task).start();
combined with the Task's updateProgress() method in the call function, it works fine, the progressBar is updated.
Question one : why do I fail at updating the ProgressBar by setting directly its progress inside of the loop (progressProperty being not binded) ? Same occurs if I want to set it (non-)visible inside of the loop.
Question 2 : Let the progressProperty be binded to the Task.progressProperty. Why can't I update the progressBar by calling the Task with Plateform.runLater(task) ? It won't update the GUI thread.
Question 3 : how do I set the visibility of the progressBar inside of the loop?
public class PRogressBar extends Application {
ProgressBar progressBar;
#Override
public void start(Stage stage) {
/**
Task for updating the ProgressBar
*/
Task<Void> task = new Task() {
#Override
protected Object call() throws Exception {
System.out.println("Init Task");
// progressBar.setVisible(false) // Question 3
// progressBar.setProgress(0.75) // question 1
for (int i = 1; i < 5; i++) {
final int update = i;
try {
Thread.sleep(500);
System.out.println("Run later : " + update/5.0);
updateProgress(i, 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
};
/*
Initializing the GUI
*/
progressBar = new ProgressBar();
Button button = new Button("blou");
button.setOnAction((event) -> {
// Platform.runLater(task); // Question 2
Thread th = new Thread(task);
// th.setDaemon(true);
th.start();
System.out.println("Thread started");
});
StackPane layout = new StackPane();
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
HBox hbox = new HBox(progressBar, button);
layout.getChildren().add(hbox);
progressBar.setProgress(0.1);
// setVisible2(false);
// progressBar.progressProperty().bind(task.progressProperty());
stage.setScene(new Scene(layout));
stage.show();
}
Question one : why do I fail at updating the ProgressBar by setting directly its progress inside of the loop (progressProperty being not binded) ? Same occurs if I want to set it (non-)visible inside of the loop.
Becouse all changes of the UI that effects the current displayed Stage have to executed in JavaFX Application Thread. Your Task is executed in your own Thread, so make sure to call setProgress and setVisible in the JavaFX Application Thread. You have to make this by Platform.runLater(() -> { //change UI});
updateProgress and updateMessage are thread-safe. Maybe these methods effects the UI, e. g. if you have bind the progressProperty to a ProgressBar. You can call it from the worker thread in a safe manner. But updateProgress is the exception not the rule.
Question 2 : Let the progressProperty be binded to the Task.progressProperty. Why can't I update the progressBar by calling the Task with Plateform.runLater(task) ? It won't update the GUI thread.
Do you mean Platform.runLater(() -> myTask.call());? This should work, becouse call get executed in the JavaFX Application Thread, so you can make changes to the UI. But this is not the way you should work with Tasks :)
Question 3 : how do I set the visibility of the progressBar inside of the loop?
You are outside of the JavaFX Application Thread so you have to use Platform.runLater(...) for it.
By the way, Brian Goetz describes in his book Java Concurrency in Practice on Chapter 9.1 Why are GUIs single-threaded?.

Mouse Clicks being cached?

I have a java swing application with a login screen. The login screen has a submit button for pressing after the user's credentials have been entered. When the button is pressed, the a wait cursor is thrown up over the window using its glass pane. There is also a default mouse adapter that does nothing for any mouse action.
private final static MouseAdapter mouseAdapter =
new MouseAdapter() {};
/** Sets cursor for specified component to Wait cursor */
public static void startWaitCursor(JComponent component) {
log.debug("startWaitCursor()");
RootPaneContainer root =
((RootPaneContainer) component.getTopLevelAncestor());
Component glass = root.getGlassPane();
glass.setCursor(WAIT_CURSOR);
glass.addMouseListener(mouseAdapter);
glass.setVisible(true);
//force repaint of glass pane in 20ms for more responsive GUI
glass.repaint(20);
}
public static void stopWaitCursor(JComponent component) {
log.debug("stopWaitCursor()");
RootPaneContainer root =
((RootPaneContainer) component.getTopLevelAncestor());
Component glass = root.getGlassPane();
glass.setCursor(DEFAULT_CURSOR);
glass.removeMouseListener(mouseAdapter);
//force repaint of glass pane in 20ms for more responsive GUI
glass.repaint(20);
glass.setVisible(false);
}
I had assumed that this setup protected me against multiple clicks/keypresses while the backend methods were taking place. I found out that this was not the case. So in the ButtonListener.actionPerformed, I put some logic like the following:
static boolean waiting = false;
class ButtonListener implements ActionListener {
ButtonListener() {
super();
}
public void actionPerformed(ActionEvent e) {
log.info("LoginWindow.ButtonListener.actionPerformed()");
LoginWindow.this.repaint(50);
if (!waiting) {
try {
waiting = true;
verifyLogin();
} finally {
waiting = false;
}
}
}
}
I found that this protected me against keypresses, but not mouse clicks! If I repeatedly press the submit button while verifyLogin() is executing, the mouse clicks seem to be being cached somewhere, and after verify login finishes, each mouse click is processed!
I am extremely puzzled about what is going on here. Does someone have an idea?
Update:
Hmm, by following the methodology suggested by Cyrille Ka: i.e. executing the verifyLogin() method in a separate thread and disabling the button, I now only get TWO events after multiple mouse clicks but the second one still annoys.
Code is now:
public void actionPerformed(ActionEvent e) {
loginButton.setEnabled(false);
log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
LoginWindow.this.repaint(50);
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
verifyLogin();
loginButton.setEnabled(true);
}});
}
but the second event still gets in. My log shows me that the second event took place about 280 ms after the first, but did not execute until 4 seconds later, in spite of the fact that setEnabled() was the first thing the actionPerformed() event did.
2013-11-13 10:33:57,186 [AWT-EventQueue-0] INFO
c.a.r.s.c.g.LoginWindow -
LoginWindow.ButtonListener.actionPerformed(). Event occurred at Nov 13
2013 10:33:57.175 2013-11-13 10:34:01,188 [AWT-EventQueue-0] INFO
c.a.r.s.c.g.LoginWindow -
LoginWindow.ButtonListener.actionPerformed(). Event occurred at Nov 13
2013 10:33:57.453
I suppose I could do a hack and discard events over a second old or something, but that feels ugly. This should not be so difficult, I keep thinking.
Update 2:
comment from JComponent.java for setEnabled()
* <p>Note: Disabling a lightweight component does not prevent it from
* receiving MouseEvents.
Since all of the Swing components are lightweight, and setEnabled does not prevent the component from receiving mouse events, what does prevent this?
I had assumed that this setup protected me against multiple clicks/keypresses while the backend methods were taking place. I found out that this was not the case.
The section from the Swing tutorial on The Glass Pane gives an example of how you might do this. Don't remember if it only handles MouseEvents or KeyEvents as well.
In any case you can also check out Disabled Glass Pane, which does handle both events.
I presume verifyLogin() is blocking until the login is done. By doing this, you are just blocking the Swing event dispatcher thread. The events from the OS still are queuing to be sent to your GUI when the thread will be available.
There are two ways to prevent your user clicking repeatidly:
Just disable the button: button.setEnabled(false); and enable it back when the process is finished.
Launch a modal dialog (for example with a wait animation) and remove it when the process is finished.
Edit: In general, you should return quickly from event listeners, since you don't want to block all your GUI, only certain part, and in any case it makes your app feel sluggish (the window won't repaint in the meantime if it is moved or other stuff). Use Thread to launch a task running a verifyLogin() and disable your button in the meantime.
This works:
class ButtonListener implements ActionListener {
long previousEventEnd;
public void actionPerformed(ActionEvent e) {
if (e.getWhen() <= previousEventEnd ) {
log.tracef("discarding stale event, event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
return;
}
log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
LoginWindow.this.repaint(50);
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
verifyLogin();
previousEventEnd = System.currentTimeMillis();
}
});
}
}
I have to admit I'm astonished. I usually defend Java to its detractors. Here I have no defense at this point. This should not be necessary.

JProgressBar indeterminate thread

I have a problem while creating a JProgressBar which is set to indeterminate.
The following code is my implementation of the JProgressBar and is called/constructed from another class:
public class Progress implements Runnable
{
private JFrame frameProgress;
private JProgressBar progressBar;
public Progress(String title, String message)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e)
{
GlobalVariables.LOGGING_logger.error("Error instatiating progress bar.",
e);
}
UIManager.put("ProgressBar.selectionForeground", Color.black);
UIManager.put("ProgressBar.selectionBackground", Color.black);
this.frameProgress = new JFrame(title);
this.frameProgress.setIconImage(GlobalVariables.GUI_icon.getImage());
this.frameProgress.setSize(300, 60);
this.frameProgress.setLocation(16, 16);
this.progressBar = new JProgressBar();
this.progressBar.setStringPainted(true);
this.progressBar.setString(message);
this.progressBar.setIndeterminate(true);
this.frameProgress.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frameProgress.add(this.progressBar);
this.frameProgress.setResizable(false);
this.frameProgress.setVisible(true);
}
public void start()
{
new Thread(this).start();
}
public void close()
{
this.frameProgress.dispose();
this.frameProgress = null;
this.progressBar = null;
}
#Override
public void run()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
}
}
The caller of this JProgressBar is the following code snippet:
Progress p = new Progress("bla", "blub");
p.start();
boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this);
p.close();
And now i want, while the xmlWriter.writeCommonSettingsFromGUI(this); is doing something, that the JProgressBar is shown to the user and is working while the algorithm is running.
How can I achieve this? I don't know so much about threading and searched in many other forums, but I don't found any answer for my question.
Please help me and thank you in advance ;)
EDIT:
The Progress JFrame opens up with no content for that time, the algorithm is running.
You are probably facing concurrency issues with Swing. Assuming that the following code runs on the EDT (Event Dispatching Thread):
Progress p = new Progress("bla", "blub");
eventually, this will open a JFrame with a progress bar in it.
I would consider using a JDialog instead of a JFrame
I would not force the size of the JFrame, but rather call pack()
Then, still running on the EDT (and thus blocking all UI-events such as repaint, mouse clicks, etc...), you call p.start() which starts a new Thread() which will invoke run() which itself calls
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
This basically won't do anything except push an additional event on the EventQueue and it will run after all currently pending events. This event will run... "nothing" since your Runnable is just empty. The new Thread dies almost immediately. So all this code is useless.
Still pursuing on the EDT, you call boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this); (btw, "successful" ends with only one 'l'). This will continue on blocking the EDT, preventing repaints from occurring and preventing the JProgressBar from painting itself. Eventually you will dispose the JFrame but since all this code is running on the EDT, the user will not see much of the progress bar and the UI will look frozen.
Consider reading the Swing tag wiki (especially the very last part with 3 important links).
Using a SwingWorker should help you out in this.

Categories

Resources