I'm trying to update the GUI status based on a time consuming task. When I push on a button, I want the button to be inactive and the cursor to change until the job is completed. I've come up with this code that mostly works as needed.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class TestWait2 extends Application {
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
primaryStage.setScene(new Scene(root));
MyService myService = new MyService();
primaryStage.getScene().getRoot().cursorProperty()
.bind(Bindings.when(myService.runningProperty())
.then(Cursor.WAIT).otherwise(Cursor.DEFAULT));
Button startButton = new Button();
startButton.setText("Button");
startButton.disableProperty().bind(myService.runningProperty());
root.getChildren().add(startButton);
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
myService.start();
}
});
primaryStage.show();
}
private class MyService extends Service<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(5000);
return null;
}
};
}
}
public static void main(String[] args) {
launch(args);
}
}
When I launch it works great the first time. The problem is that if I click on the button a second time it get an error.
Exception in thread "JavaFX Application Thread" java.lang.IllegalStateException: Can only start a Service in the READY state. Was in state SUCCEEDED
Any thoughts on how to get around that issue?
I'm running on Java 8u5.
The problem with your code that you try to call myService.start(); when its on SUCCEEDED state(since you started it already once).
this is according to javadoc of Service start method
Starts this Service. The Service must be in the READY state to succeed in * *this call.
This method should only be called on the FX application thread.
to make your code work, you need to you call myService.restart().
since you are planning to use your service over and over you can do the following:
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//replace this line
// myService.start();
//with this
myService.restart();
}
});
Adding this to the program seemed to solve this.
myService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
myService.reset();
}
});
I also found I can add the following directly to the MyService class.
#Override
protected void succeeded() {
reset();
}
There doesn't seem to be any other way to have it go to the ready state after completing it's work.
Related
I'm creating a game in JavaFX (something like Space Invaders) and I'm having problems with the shooting sound, particularly when I press a key quite a few times not only the sound stops being played but other sounds also stop working.
I've done some small research and it seems that this kind of problem is fairly popular and it involves releasing the MediaPlayer object/instance but I can't call that method(?).
I've tried using dispose() method but it disables the shot sound completely.
I have two classes, GameApp:
primaryStage.getScene().setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.SPACE && playable) {
Audio.playPlayerShotSound();
}
}
});
and Audio:
public class Audio {
private static Media shot = new Media(new File("resources/playerShot.wav").toURI().toString());
public static void playPlayerShotSound() {
MediaPlayer shotSound = new MediaPlayer(shot);
shotSound.setVolume(0.2);
shotSound.play();
}
I've created another class using JavaFX and the sound stops after pressing Space 64 times.
package examples;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
import javafx.scene.layout.Pane;
import java.io.File;
public class GameInst extends Application {
Pane root;
private AnimationTimer timer;
private static Media shot = new Media(new File("resources/playerShot.wav").toURI().toString());
int count = 0;
private Parent createContent() {
root = new Pane();
root.setPrefSize(500, 500);
timer = new AnimationTimer() {
#Override
public void handle(long now) {
onUpdate();
}
};
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
onUpdate();
}
};
timer.start();
return root;
}
private void onUpdate() {
}
#Override
public void start(Stage primaryStage) throws Exception {
root = new Pane();
primaryStage.setTitle("Space Invaders");
primaryStage.setScene(new Scene(createContent()));
primaryStage.getScene().setOnKeyPressed(event -> {
switch (event.getCode()) {
case SPACE:
MediaPlayer shotSound = new MediaPlayer(shot);
shotSound.setVolume(0.1);
shotSound.play();
count++;
System.out.println(count);
}
});
primaryStage.show();
}
public static void main (String[] args){
launch(args);
}
}
For what you are trying to do the AudioClip class is probably much better suited as the full blown MediaPlayer. It also probably not a good idea to initialize a new player every time you get an event.
I have an application which runs in background. With jkeymaster I have registered a global hotkey to be fired although my application is not the active one. This is working. I now want to switch my background app in foreground again. Tried with this code:
import com.tulskiy.keymaster.common.HotKey;
import com.tulskiy.keymaster.common.HotKeyListener;
import com.tulskiy.keymaster.common.Provider;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
public class TestClientNotWorking extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws AWTException {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Btn said 'Hello World!'");
}
});
HBox root = new HBox(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
primaryStage.requestFocus();
Provider provider = Provider.getCurrentProvider(false);
provider.register(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,
InputEvent.ALT_MASK | InputEvent.SHIFT_MASK),
new HotKeyListener() {
#Override
public void onHotKey(HotKey hotKey) {
Platform.runLater(new Runnable() {
#Override
public void run() {
System.out.println("Recievd");
primaryStage.toFront();
btn.requestFocus();
}
});
}
});
}
}
What happens:
My stage is brought back to foreground, but the focus still remains in the app, were it was before and is not transferred to my primaryStage. Can you please help my how I could gain this?
Thanks in advance
I compiled and run the code. As far as I understand your expectations are:
start application, register key hook
minimize jfx window
press key combination (alt shift space in your case)
result: jfx window is in foreground state, 'Hello World' button is focused
btn.requestFocus() works properly on my OS without any changes but my guess you need to try setIconified\setMaximized instead of primaryStage.toFront();
I changed a little bit your code pls try it and let us know if it works as you want.
public class TestClientNotWorking extends Application {
private final Provider provider = Provider.getCurrentProvider(false);
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) throws AWTException {
final Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent event) {
System.out.println("Btn said 'Hello World!'");
}
});
final HBox root = new HBox(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
root.requestFocus();// remove focus from btn
final KeyStroke stroke = KeyStroke.getKeyStroke("ctrl SPACE");// stroke = KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK | InputEvent.ALT_MASK);
provider.register(stroke, new HotKeyListener() {
#Override
public void onHotKey(final HotKey hotKey) {
Platform.runLater(new Runnable() {
#Override
public void run() {
System.out.println("Recievd");
primaryStage.setIconified(false); // primaryStage.toFront();
btn.requestFocus(); // set focus on btn
}
});
}
});
}
#Override
public void stop() throws Exception {
super.stop();
provider.stop();
}
}
I am a student and learning JavaFX since a month.
I am developing a application where I want a service to repeatedly start again after its execution of the task. For this I have come to know that 'ScheduledService' is used.
So can anybody please explain the use of scheduledservice with simple example and also how it differs from the 'Service' in JavaFX. Thanks ;)
EDIT : How can I define that this ScheduledService named DataThread should be restarted every 5 seconds ?
public class DataThread extends ScheduledService<Void>
{
#Override
public Task<Void> createTask() {
return new Task<Void>() {
#Override
public Void call() throws Exception {
for(i=0;i<10;i++)
{
System.out.println(""+i);
}
return null;
}
};
}
}
Considering you have a sound knowledge of Service class. ScheduledService is just a Service with a Scheduling functionality.
From the docs
The ScheduledService is a Service which will automatically restart itself after a successful execution, and under some conditions will restart even in case of failure
So we can say it as,
Service -> Execute One Task
ScheduledService -> Execute Same Task at regular intervals
A very simple example of Scheduled Service is the TimerService, which counts the number of times the Service Task has been called. It is scheduled to call it every 1 second
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TimerServiceApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TimerService service = new TimerService();
AtomicInteger count = new AtomicInteger(0);
service.setCount(count.get());
service.setPeriod(Duration.seconds(1));
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
System.out.println("Called : " + t.getSource().getValue()
+ " time(s)");
count.set((int) t.getSource().getValue());
}
});
service.start();
}
public static void main(String[] args) {
launch();
}
private static class TimerService extends ScheduledService<Integer> {
private IntegerProperty count = new SimpleIntegerProperty();
public final void setCount(Integer value) {
count.set(value);
}
public final Integer getCount() {
return count.get();
}
public final IntegerProperty countProperty() {
return count;
}
protected Task<Integer> createTask() {
return new Task<Integer>() {
protected Integer call() {
//Adds 1 to the count
count.set(getCount() + 1);
return getCount();
}
};
}
}
}
I have a single controller class "FXController.java" and my FXApplication.java that extends "Application" and contains the launch code. In a separate class "TestFX.java" I call the start method in the "FXApplication.java" that starts the gui. I want to be able to access its controller so that I can change the text within a textfield of the controller. In my FXApplication.java, within the "launch" method I create a variable for the FXLoader and use the "getController" method and set it to a public variable: public FXController theController.
Within the TestFX.java, after I call the "start" method in the main method that launches FXApplication.java in a new runnable, I try to access the controller to change the contents of a single textfield, I get an exception that says controller is null. What is the proper way for me to change the contents of the textfield? I feel that the threading is causing problems.
What I am trying to do in my main method is:
Launch the JavaFX Application/GUI
5 seconds later (sleep), change the text of the text field in FXController.java to "Hello World".
Note that the fxml file loaded/used by FXApplication.java is pointed correctly to the FXController.java. Am wondering if there is some way to access the controller despite having spawned a new runnable for the FX application.
FXApplication.java
public class FXApplication extends Application {
public FXController theController;
public void start() {
Application.launch(FXApplication.class, args);
}
#Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmll = new FXMLLoader(getClass().getResource("fxml_example.fxml"));
Parent root = fxmll.load();
theController = fxmll.getController();
stage.setTitle("FXML Welcome");
stage.setScene(new Scene(root, 300, 275));
stage.show();
}
}
My TestFX.java
public class TestFX {
public static FXApplication fxApp = new FXApplication();
public ExecutorService execs;
public Future<?> fut;
TestFX(ExecutorService execs) {
this.execs = execs;
}
public void start() {
fut = execs.submit(new Runnable() {
#Override
public void run() {
fxApp.start();
}
});
}
public static void main(String[] args) {
ExecutorService execs = Executors.newCachedThreadPool();
TestFX testFx = new TestFX(execs);
testFx.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//fxApp.theController.setTextBoxText("Hello Word");
Platform.runLater(() -> fxApp.theController.setTextBoxText("Hello Word"));
}
Stuff that you want to do like this, you should use the Task class. This does all the heavy lifting for you and all you have to do is set up the code you want to run the FXAT when the task completes. Here's an example, I've left out the controller stuff, because it just clutters up the concepts:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
private static Label label;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
label = new Label();
label.setText("Waiting...");
StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
return null;
}
};
sleeper.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
label.setText("Hello World");
}
});
new Thread(sleeper).start();
}
}
The "sleeper" Task doesn't do anything except sleep, but it's going to sleep on a new thread so the FXAT can keep on responding to screen activity. Then when the sleep finishes, the event handler for the succeed will run on the thread that instantiated the Task, in this case the FXAT.
Your code has two problems.
First, you are calling the static FXMLLoader.load(URL) method, instead of calling load on your FXMLLoader instance. Consequently, the FXMLLoader instance never gets to initialize its controller. You need
FXMLLoader fxmll = new FXMLLoader(getClass().getResource("fxml_example.fxml"));
Parent root = fxmll.load();
The second issue is that you are then changing the text of the text box from a background thread, instead of from the FX Application Thread. (Unless you're handling this in the controller class: you don't show the code for that.) You need
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(() -> fxApp.theController.setTextBoxText("Hello Word"));
You can also do this with a PauseTransition:
PauseTransition pause = new PauseTransition(Duration.seconds(5));
pause.setOnFinished(event -> fxApp.theController.setTextBoxText("Hello Word"));
pause.play();
I have a sample Hello World JavaFx. I am using Eclipse and eFxclipse plugin. My Eclipse is kepler which is Eclipse 4.3.2 version and Java servion is Jdk1.7-045.
What I try to add is very little concurrency codes, I just want to update button text in the example. Could this backend task interact with upfront UI control, for example button, scene? If not, how could I make tailored backend task, then interact with UI control?
Thanks in advance
package com.juhani.fx.exer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application{
private static final short COREPOOLSIZE=2;
private static final short MAXIMUMPOOLSIZE=2;
private static final int WORKQUEUECAPACITY=100;
private static Logger log = LogManager.getLogger(
HelloWorld.class.getName());
private ExecutorService executors = new ThreadPoolExecutor(COREPOOLSIZE,MAXIMUMPOOLSIZE,20,TimeUnit.MINUTES,new ArrayBlockingQueue<Runnable>(WORKQUEUECAPACITY));
public static void main(String[] args) {
LogMessage logMessage = new LogMessage("BEGIN",1.0,1,"HELLOWORLD");
log.trace(logMessage.toString());
launch(args);
}
#Override
public void start(final Stage primaryStage) throws InterruptedException {
primaryStage.setTitle("Hello World!");
final Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
final StackPane root = new StackPane();
root.getChildren().add(btn);
final Scene scene= new Scene(root,300,250);
primaryStage.setScene(scene);
primaryStage.show();
Task<Boolean> task = new Task<Boolean>() {
#Override
public Boolean call() {
for(int i=0;i<20;i++){
btn.setText("First row\nSecond row "+i);
primaryStage.show();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error(new LogMessage("entering interruption",1.0,2,"exception").toString());
}
}
return new Boolean(true);
}
};
executors.submit(task);
}
}
This answer specially talks about the use of Platform.runLater. If you are using Task, you are better off updating the UI using the method it provides as stated in
kleopatra's answer.
For updating the UI, you have to be on the Javafx thread.
Once you are on any other thread, use Platform.runLater() to update those data back to Javafx UI. A working example can be found below
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
private static final short COREPOOLSIZE = 2;
private static final short MAXIMUMPOOLSIZE = 2;
private static final int WORKQUEUECAPACITY = 100;
private ExecutorService executors = new
ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE, 20, TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(WORKQUEUECAPACITY));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) throws InterruptedException {
primaryStage.setTitle("Hello World!");
final Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
final StackPane root = new StackPane();
root.getChildren().add(btn);
final Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
Task<Boolean> task = new Task<Boolean>() {
#Override
public Boolean call() {
final AtomicInteger i = new AtomicInteger(0);
for( ; i.get() < 20; i.incrementAndGet()) {
Platform.runLater(new Runnable() {
#Override
public void run() {
btn.setText("First row\nSecond row " + i);
}
});
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {}
}
return Boolean.valueOf(true);
}
};
executors.submit(task);
}
}
For more information you can go through the links provided here
A Task is designed to interact with the ui on the fx-application thread, to take advantage of that support you should use it as designed :-)
As a general rule, you must not access ui in the call method [*] of the Task. Instead, update one of its properties (message, progress ...) and bind that property to your ui. Sample code:
Task<Boolean> taskWithBinding = new Task<Boolean>() {
#Override
public Boolean call() {
final AtomicInteger i = new AtomicInteger(0);
for( ; i.get() < 20; i.incrementAndGet()) {
updateMessage("First row\nSecond row " + i);
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
};
btn.textProperty().bind(taskWithBinding.messageProperty());
[*] The one exception is outlined (wrap the access into an runLater) in the other answer. Doing so is technically correct - but then you are by-passing a Task's abilities and could use an arbitrary Runnable ...