Sound stops working after pressing a key 53 times - javafx

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.

Related

Regain javafx focus

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();
}
}

Multiple independent stages in JavaFX

Is there a way to launch multiple independent stages in JavaFX? By independent I mean that the stages are all created from the main thread.
At the moment my application is more or less an algorithm where I would like to plot some charts and tables during execution (mainly to check whether the results are correct/ to debug).
The problem is that I cannot figure out how to create and show multiple stages independently, i.e. I would like to do something like this
public static void main(){
double[] x = subfunction.dosomething();
PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
double[] y = subfunction.dosomethingelse();
PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
.....
}
which would allow to use PlotUtil as one would use the plotting functions in other scripting languages (like Matlab or R).
So the main question is how to "design" PlotUtils? So far I tried two things
PlotUtils uses Application.launch for each plot call (creating a new stage with a single scene every time) --> does not work as Application.launch can only be invoked once.
Create some kind of "Main Stage" during the first call to PlotUtils, get a reference to the created Application and start subsequent stages from there --> does not work as using Application.launch(SomeClass.class) I am not able to get a reference to the created Application instance.
What kind structure/design would allow me to implement such a PlotUtils function?
Update 1:
I came up with the following idea and was wondering whether there are any major mistakes in this solution.
Interface to be implemented by all "Plots"
public abstract class QPMApplication implements StageCreator {
#Override
public abstract Stage createStage();
}
Plotting functionality:
public class PlotStage {
public static boolean toolkitInialized = false;
public static void plotStage(String title, QPMApplication stageCreator) {
if (!toolkitInialized) {
Thread appThread = new Thread(new Runnable() {
#Override
public void run() {
Application.launch(InitApp.class);
}
});
appThread.start();
}
while (!toolkitInialized) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Platform.runLater(new Runnable() {
#Override
public void run() {
Stage stage = stageCreator.createStage();
stage.show();
}
});
}
public static class InitApp extends Application {
#Override
public void start(final Stage primaryStage) {
toolkitInialized = true;
}
}
}
Using it:
public class PlotStageTest {
public static void main(String[] args) {
QPMApplication qpm1 = new QPMApplication() {
#Override
public Stage createStage() {
Stage stage = new Stage();
StackPane root = new StackPane();
Label label1 = new Label("Label1");
root.getChildren().add(label1);
Scene scene = new Scene(root, 300, 300);
stage.setTitle("First Stage");
stage.setScene(scene);
return stage;
}
};
PlotStage.plotStage(qpm1);
QPMApplication qpm2 = new QPMApplication() {
#Override
public Stage createStage() {
Stage stage = new Stage();
StackPane root = new StackPane();
Label label1 = new Label("Label2");
root.getChildren().add(label1);
Scene scene = new Scene(root, 300, 200);
stage.setTitle("Second Stage");
stage.setScene(scene);
return stage;
}
};
PlotStage.plotStage(qpm2);
System.out.println("Done");
}
}
The easiest approach here would be just to refactor your application so that it is driven from the FX Application thread. For example, you could rewrite your original code block as
public class Main extends Application {
#Override
public void start(Stage primaryStageIgnored) {
double[] x = subfunction.dosomething();
PlotUtil.plot(x); //creates a new window and shows some chart/table etc.
double[] y = subfunction.dosomethingelse();
PlotUtil.plot(y); //creates a new window and shows some chart/table etc.
// .....
}
public static void main(String[] args) {
launch(args);
}
}
Now PlotUtil.plot(...) merely creates a Stage, puts a Scene in it, and show()s it.
This assumes the methods you're calling don't block, but if they do you just have to wrap them in a Task and call PlotUtils.plot(...) in the onSucceeded handler for the task.
If you really want to drive this from a non-JavaFX application, there's a fairly well-known hack to force the JavaFX Application thread to start if it's not already started, by creating a new JFXPanel. A JFXPanel should be created on the AWT event dispatch thread.
Here's a very basic example of the second technique. Start the application and type "show" into the console. (Type "exit" to exit.)
import java.util.Scanner;
import java.util.concurrent.FutureTask;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.swing.SwingUtilities;
public class Main {
private JFXPanel jfxPanel ;
public void run() throws Exception {
boolean done = false ;
try (Scanner scanner = new Scanner(System.in)) {
while (! done) {
System.out.println("Waiting for command...");
String command = scanner.nextLine();
System.out.println("Got command: "+command);
switch (command.toLowerCase()) {
case "exit":
done = true;
break ;
case "show":
showWindow();
break;
default:
System.out.println("Unknown command: commands are \"show\" or \"exit\"");
}
}
Platform.exit();
}
}
private void showWindow() throws Exception {
ensureFXApplicationThreadRunning();
Platform.runLater(this::_showWindow);
}
private void _showWindow() {
Stage stage = new Stage();
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
Scene scene = new Scene(new StackPane(button), 350, 75);
stage.setScene(scene);
stage.show();
stage.toFront();
}
private void ensureFXApplicationThreadRunning() throws Exception {
if (jfxPanel != null) return ;
FutureTask<JFXPanel> fxThreadStarter = new FutureTask<>(() -> {
return new JFXPanel();
});
SwingUtilities.invokeLater(fxThreadStarter);
jfxPanel = fxThreadStarter.get();
}
public static void main(String[] args) throws Exception {
Platform.setImplicitExit(false);
System.out.println("Starting Main....");
new Main().run();
}
}
Here is something more along the lines I would actually follow, if I wanted the user to interact via the OS terminal (i.e. using System.in). This uses the first technique, where the application is driven by an FX Application subclass. Here I create two background threads, one to read commands from System.in, and one to process them, passing them via a BlockingQueue. Even though nothing is displayed in the main FX Application Thread, it is still a very bad idea to block that thread waiting for commands. While the threading adds a small level of complexity, this avoids the "JFXPanel" hack, and doesn't rely on there being an AWT implementation present.
import java.util.Scanner;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class FXDriver extends Application {
BlockingQueue<String> commands ;
ExecutorService exec ;
#Override
public void start(Stage primaryStage) throws Exception {
exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
commands = new LinkedBlockingQueue<>();
Callable<Void> commandReadThread = () -> {
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.print("Enter command: ");
commands.put(scanner.nextLine());
}
}
};
Callable<Void> commandProcessingThread = () -> {
while (true) {
processCommand(commands.take());
}
};
Platform.setImplicitExit(false);
exec.submit(commandReadThread);
exec.submit(commandProcessingThread);
}
private void processCommand(String command) {
switch (command.toLowerCase()) {
case "exit":
Platform.exit();
break ;
case "show":
Platform.runLater(this::showWindow);
break;
default:
System.out.println("Unknown command: commands are \"show\" or \"exit\"");
}
}
#Override
public void stop() {
exec.shutdown();
}
private void showWindow() {
Stage stage = new Stage();
Button button = new Button("OK");
button.setOnAction(e -> stage.hide());
Scene scene = new Scene(new StackPane(button), 350, 75);
stage.setScene(scene);
stage.show();
stage.toFront();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX - Control and Concurrency

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 ...

Redraw javafx scene [duplicate]

I'm trying to simulate a basic thermostat in an application GUI.
I want to update a label box value every 2 secs with the new temperature value.
For example, my intial temperature will be displayed as 68 degrees and updated to 69, to 70, etc. till 75 every 2 seconds.
This is a piece of code I wrote in Java fx. controlpanel is object of te form where the label box is present. It updates only the final value as 75. It doesnt update it every 2 secs. I have written a method pause to cause a 2 secs delay. All labels are updated with their final values but not updated every 2 secs. When I debug, I can see that the values are increased by one every 2 secs. This code is written in button onClick event
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
int i=0;
Timer asd = new Timer(1000,null);
asd.setDelay(1000);
while(i < 10)
{
jTextField1.setText(Integer.toString(i));
i++;
asd.start();
}
}
To solve your task using Timer you need to implement TimerTask with your code and use Timer#scheduleAtFixedRate method to run that code repeatedly:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
System.out.print("I would be called every 2 seconds");
}
}, 0, 2000);
Also note that calling any UI operations must be done on Swing UI thread (or FX UI thread if you are using JavaFX):
private int i = 0;
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
jTextField1.setText(Integer.toString(i++));
}
});
}
}, 0, 2000);
}
In case of JavaFX you need to update FX controls on "FX UI thread" instead of Swing one. To achieve that use javafx.application.Platform#runLater method instead of SwingUtilities
Here is an alternate solution which uses a JavaFX animation Timeline instead of a Timer.
I like this solution because the animation framework ensures that everything happens on the JavaFX application thread, so you don't need to worry about threading issues.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Random;
public class ThermostatApp extends Application {
#Override public void start(final Stage stage) throws Exception {
final Thermostat thermostat = new Thermostat();
final TemperatureLabel temperatureLabel = new TemperatureLabel(thermostat);
VBox layout = new VBox(10);
layout.getChildren().addAll(temperatureLabel);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;");
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
class TemperatureLabel extends Label {
public TemperatureLabel(final Thermostat thermostat) {
textProperty().bind(
Bindings.format(
"%3d \u00B0F",
thermostat.temperatureProperty()
)
);
}
}
class Thermostat {
private static final Duration PROBE_FREQUENCY = Duration.seconds(2);
private final ReadOnlyIntegerWrapper temperature;
private final TemperatureProbe probe;
private final Timeline timeline;
public ReadOnlyIntegerProperty temperatureProperty() {
return temperature.getReadOnlyProperty();
}
public Thermostat() {
probe = new TemperatureProbe();
temperature = new ReadOnlyIntegerWrapper(probe.readTemperature());
timeline = new Timeline(
new KeyFrame(
Duration.ZERO,
new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
temperature.set(probe.readTemperature());
}
}
),
new KeyFrame(
PROBE_FREQUENCY
)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}
class TemperatureProbe {
private static final Random random = new Random();
public int readTemperature() {
return 72 + random.nextInt(6);
}
}
The solution is based upon the countdown timer solution from: JavaFX: How to bind two values?
Calling Platform.runLater worked for me:
Platform.runLater(new Runnable() {
#Override
public void run() {
}
});

Javafx service to update GUI objects status

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.

Resources