javafx how to use thread in fxml controller - java

I want to use thread I can use in simple program, but I can't use threads in fxml controller
Simple program:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javafxapplication3;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #web http://java-buddy.blogspot.com/
*/
public class JavaFX_TimerTask extends Application {
final int MAX = 100;
Thread myTaskThread;
Thread myRunnableThread;
Timer myTimer;
MyTask myTask;
MyRunnable myRunnable;
MyTimerTask myTimerTask;
#Override
public void start(Stage primaryStage) {
myTask = new MyTask();
ProgressBar progressBarTask = new ProgressBar();
progressBarTask.setProgress(0);
progressBarTask.progressProperty().bind(myTask.progressProperty());
ProgressBar progressBarRunnable = new ProgressBar();
progressBarRunnable.setProgress(0);
myRunnable = new MyRunnable(progressBarRunnable);
ProgressBar progressBarTimerTask = new ProgressBar();
progressBarTimerTask.setProgress(0);
myTimerTask = new MyTimerTask(progressBarTimerTask);
Button btnStart = new Button("Start Task");
btnStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
myTaskThread = new Thread(myTask);
myTaskThread.start();
myRunnableThread = new Thread(myRunnable);
myRunnableThread.start();
myTimer = new Timer();
myTimer.scheduleAtFixedRate(myTimerTask, 80, 100);
}
});
VBox vBox = new VBox();
vBox.setPadding(new Insets(5, 5, 5, 5));
vBox.setSpacing(5);
vBox.getChildren().addAll(
new Label("Run in Thread(Task)"),
progressBarTask,
new Label("Run in Thread(Runnable)"),
progressBarRunnable,
new Label("Run in Timer and TimerTask"),
progressBarTimerTask,
btnStart);
StackPane root = new StackPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("java-buddy.blogspot.com");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyTask extends Task<Void>{
#Override
protected Void call() throws Exception {
for (int i = 1; i <= MAX; i++) {
updateProgress(i, MAX);
Thread.sleep(100);
}
return null;
}
}
class MyRunnable implements Runnable{
ProgressBar bar;
public MyRunnable(ProgressBar b) {
bar = b;
}
#Override
public void run() {
for (int i = 1; i <= MAX; i++) {
final double update_i = i;
//Not work if update JavaFX UI here!
//bar.setProgress(i/MAX);
//Update JavaFX UI with runLater() in UI thread
Platform.runLater(new Runnable(){
#Override
public void run() {
bar.setProgress(update_i/MAX);
}
});
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(JavaFX_TimerTask.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
class MyTimerTask extends TimerTask{
ProgressBar bar;
double count;
public MyTimerTask(ProgressBar b) {
bar = b;
count = 0;
}
#Override
public void run() {
bar.setProgress(count++/MAX);
if(count >= MAX){
myTimer.cancel();
}
}
}
}
Now, I want to use thread in a fxml controller:
public class DashboardController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
When I use thread, in initialize it doesn't show me any output.
How can I use thread?
Thank you.

JavaFx already runs threads -
JavaFx thread for GUI
Launch thread for background services.
If you need to make something like progress bar in which you want to run something over javafx thread then i would suggest use Services instead of thread as it can be used again and again while threads can't be.
Service<Void> ser = new Service<Void>() {
#Override protected Task createTask() {
return new Task<Void>() {
#Override protected Void call() throws InterruptedException {
// You code you want to execute in service backgroundgoes here
return null;
}
};
}
};
ser.setOnSucceeded((WorkerStateEvent event) -> {
// Anything which you want to update on javafx thread (GUI) after completion of background process.
});
ser.start();
You can use the service again and again with any variation like loop/recursion/switch -
ser.restart(); // Restart the service
ser.reset(); // Stops the service

Is your Controller initialized?
Do you set it (in the fxml/FXMLoader)?
If it your Controller is loaded this should work.
public class DashboardController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
myTask = new MyTask();
myTaskThread = new Thread(myTask);
myTaskThread.start();
}
}

Related

JavaFX Simple Update Label (Threading)

I'm trying to demonstrate to a few beginner programmers how to set a label on a JavaFX app to auto update. Basically they would like the value to decrease every minute or so on the label without any user interaction.
Java isn't my strong point and looking through some previous questions I get that I need to deal with threads and Runnable().
I have put the code together below that works, but I was just wondering if there is a better way of doing this or an easier way to demonstrate the same outcome with simpler code.
public class MainTimer2 extends Application {
private int count = 100;
private Label response = new Label(Integer.toString(count));
public static void main(String[] args) {
launch(args);
}
//Update function
private void decrementCount() {
count--;
response.setText(Integer.toString(count));
}
#Override
public void start(Stage myStage) {
myStage.setTitle("Update Demo");
//Vertical and horizontal gaps set to 10px
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 200, 100);
myStage.setScene(myScene);
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
Runnable updater = new Runnable() {
#Override
public void run() {
decrementCount();
}
};
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println("Timer error");
}
// UI update is run on the Application thread
Platform.runLater(updater);
}
}
});
// don't let thread prevent JVM shutdown
thread.setDaemon(true);
thread.start();
rootNode.getChildren().addAll(response);
myStage.show();
}
}
Count down by using PauseTransition:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MainTimer2 extends Application {
private int count = 100;
private Label response = new Label(Integer.toString(count));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage myStage) {
myStage.setTitle("Update Demo");
//Vertical and horizontal gaps set to 10px
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 200, 100);
myStage.setScene(myScene);
rootNode.getChildren().addAll(response);
myStage.show();
update();
}
private void update() {
PauseTransition pause = new PauseTransition(Duration.seconds(1));
pause.setOnFinished(event ->{
decrementCount();
pause.play();
});
pause.play();
}
//Update function
private void decrementCount() {
count = (count > 0) ? count -1 : 100;
response.setText(Integer.toString(count));
}
}
Alternatively you could use Timeline:
private void update() {
KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> {
decrementCount();
}
);
Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
//if you want to limit the number of cycles use
//timeline.setCycleCount(100);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}

Start and stop JavaFX background thread with 2 buttons

I am trying to make a simple UI to launch a selenium test that has the ability to start a background thread which launches a browser when the Start Button is pressed and stops the thread and closes it when the Stop button is pressed.
Unfortunately when I click stop after starting it, it does not work. If I let it finish I cannot restart the thread. How would I go about updating this so that I can make it submit a new thread that can be stopped by the stop button.
package application;
import org.openqa.selenium.WebDriver;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
public class Main extends Application {
Stage window;
GridPane grid;
public void start(Stage primaryStage) {
/*
* Set up the stage
*/
window = primaryStage;
window.setTitle("URL LOADER - V1");
grid = new GridPane();
grid.setPadding(new Insets(10,10,10,10));
grid.setVgap(8);
grid.setHgap(10);
window.setResizable(false);
/*
* URL input
*/
Label URLLabel = new Label("URL");
GridPane.setConstraints(URLLabel,0,0);
TextField URLTextField = new TextField();
URLTextField.setPromptText("https://www.google.com");
GridPane.setConstraints(URLTextField,1,0);
/*
* Create Buttons
*/
Button buttonStart = new Button("Create");
GridPane.setConstraints(buttonStart,1,6);
Button buttonStop = new Button("Stop");
GridPane.setConstraints(buttonStop,1,8);
grid.getChildren().addAll(URLLabel,URLTextField, buttonStart, buttonStop);
/*
* Create the scene
*/
Scene scene = new Scene(grid, 300, 300);
window.setScene(scene);
window.show();
Task<Void> task = new Task<Void>(){
#Override
protected Void call() {
new VisitPage().Start(this,URLTextField.getText());;
return null;
}
};
buttonStart.setOnAction(new EventHandler<ActionEvent>() {
/*
* Start Button Clicked
*/
public void handle(ActionEvent event) {
new Thread(task).start();
}
});
buttonStop.setOnAction(new EventHandler<ActionEvent>() {
/*
* Start Button Pressed
*/
public void handle(ActionEvent event) {
System.out.println("Stop Pressed");
}
});
}
public class VisitPage {
private String URL;
Browser BrowserFactory;
ThreadLocal<WebDriver> drivers;
WebDriver Browser;
public void Start(Task<Void> task, String URL) {
while (true) {
if (task.isCancelled())
{
System.out.println("Canceling...");
System.out.println("Stop Pressed");
Browser.close();
Browser.quit();
BrowserFactory.CloseDriver(drivers);
task.cancel();
}
else
{
/*
* Create Browser Factor to make ThreadLocal Browsers
*/
BrowserFactory = new Browser(1, 1);
drivers = BrowserFactory.SpawnBrowser();
/*
* Grab a Browser
*/
Browser = BrowserFactory.SpawnDriver(drivers);
/*
* Visit and scrape
*/
Browser.get(URL);
/*
* Wait 5 Seconds before closing
*/
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Browser.close();
Browser.quit();
BrowserFactory.CloseDriver(drivers);
}
}
}
}
public static void main(String[] args) {
launch(args);
}
}
According to documentation
As with FutureTask, a Task is a one-shot class and cannot be reused. See Service for a reusable Worker.
So you have to create new task for each run. So I added task as field in Main:
Stage window;
GridPane grid;
Task<Void> task;
Then create task when start button is clicked:
buttonStart.setOnAction(new EventHandler<ActionEvent>() {
/*
* Start Button Clicked
*/
#Override
public void handle(ActionEvent event) {
if(task != null) {
System.out.println("Task already running");
return;
}
task = new Task<Void>() {
#Override
protected Void call() {
new VisitPage().start(this, URLTextField.getText());
;
return null;
}
};
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
});
On stop button click you have to cancel task:
buttonStop.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if(task == null) {
System.out.println("Task not running");
return;
}
System.out.println("Stop Pressed");
task.cancel();
task = null;
}
});
This will do nothing, because it is your responsibility to end task when it is cancelled, and you are not ending your infinite loop.
So your VisitPage should look like this (I skipped testing details, since I do not have them on classpath):
public class VisitPage {
public void start(Task<Void> task, String URL) {
while (!task.isCancelled()) {
System.out.println("Running test");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Test run ended");
}
System.out.println("Canceling...");
System.out.println("Stop Pressed");
return;
}
}
Some minor points:
Technically task.cancel() would end your thread sometimes if you would not catch InterruptedException that is thrown if your thread is sleeping.
I am not sure how your code compiled but I had to make some variables final so they can be used in handlers: (never mind, from Java SE 8 local variables can be effectively final)
final TextField URLTextField = new TextField();
//...
final Task<Void> task = new Task<Void>(){
//...
I would define created thread as daemon so it will not keep running when you close your UI without stopping tests:
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
I also renamed Start method to start

Running a thread by toggling a flag externally (from GUI)

I am making a simple JavaFX college course project and I need a good way of dealing with threads, mainly running them while a certain flag is activated.
This is a simple sketch I came up with:
public class ListenerService extends Thread {
private static ArrayList<ListenerService> listeners = new ArrayList<>();
private ToggleButton button;
private File folder;
private SimpleBooleanProperty active = new SimpleBooleanProperty();
ListenerService(ToggleButton button, String pathname) {
this.button = button;
this.folder = new File(pathname);
button.setOnAction(event -> active.set(button.isSelected()));
active.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
-> {if (newValue.booleanValue()) start();});
listeners.add(this);
}
#Override
public void run() {
while(active.get())
System.out.print(".");
}
The process is as following:
The user dynamically creates a ToggleButton on the form. A
ListenerService object is created, to which a button and a directory
are assigned.
A listener is assigned to the button - if it's clicked - activate
the flag. Otherwise, deactivate. The flag here is a
SimpleBooleanProperty instance.
If the flag is switched on, run the thread. The thread will run
while the flag is active. If the user toggles the button again and
deactivates it, the condition in the while loop would fail and
thread should stop running.
As soon as I run the program, it freezes. I tried making the flag volatile, but nothing changed. Since the flag is controlled externally (from GUI), there isn't a way to make this method synchronized.
What am I doing wrong?
You basically create a new Thread that runs as long as the button is selected and exits when the button is not selected.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ThreadApp extends Application {
public class Worker implements Runnable{
private boolean active;
public void setActive(boolean active) {
this.active = active;
}
#Override
public void run() {
while(active)
{
System.out.println("Active! " + System.currentTimeMillis());
}
}
}
public class WorkerToggle extends ToggleButton {
Worker worker;
public WorkerToggle(String text) {
super(text);
this.worker = new Worker();
setOnAction((event) -> {
if(isSelected())
{
worker.setActive(true);
Thread thread = new Thread(worker);
thread.setDaemon(true);
thread.start();
}else
{
worker.setActive(false);
}
});
}
}
#Override
public void start(Stage primaryStage) {
BorderPane rootPane = new BorderPane();
Scene scene = new Scene(rootPane);
rootPane.setCenter(new WorkerToggle("toggle me"));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This should work fine, but creating Threads can be expensive, so you might want to look into ThreadPoolExecutor if you notice some performance problems there.
In JavaFX you have the ability to use a scheduled service to run things off the main FX thread. Here is simple sample that might help.
public class JavaFXApplication3 extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
PollingService service = new PollingService();
service.setPeriod(Duration.millis(1000)); // sysout every second
ToggleButton tb = new ToggleButton("Start Process");
tb.setOnAction(event -> {
System.out.println(tb.isSelected());
if(tb.isSelected()){
service.reset();
service.start();
}else {
service.cancel();
}
});
VBox vbox = new VBox(tb);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();;
}
private class PollingService extends ScheduledService<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() {
System.out.print(".#.");
return null;
}
};
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

JavaFX update text from Task

I want to change text I create a task and increment i, but I want to set a new text on this same place when i is changed, but old text doesn't disappear. It's my code. On swing I will be use repaint()
Task task = new Task<Void>() {
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
final int finalI = i;
Platform.runLater(new Runnable() {
#Override
public void run() {
String a = "aaa";
if(finalI>4){
a = "sadsa";
}
if(finalI>10){
a = "sadsadsadsadsad";
}
gc.fillText(a, 150, 250+10);
}
});
i++;
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
As I mentioned in my comment, the problem is that Canvas really acts like a drawing board. You have drawn some text on it then you have drawn another text without erasing the previous text.
In your case, when you want to store a reference to the text to be able to update it, it is more reasonable to use a Pane and put a Text instance on it.
I have created an example for you:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
Pane pane = new Pane();
Text text = new Text("");
pane.getChildren().add(text);
Task<Void> task = new Task<Void>() {
String a = "Initial text";
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
if (i > 4)
a = "I is bigger than 4";
if (i > 10)
a = "I is bigger than 10";
Platform.runLater(() -> {
text.setText(a);
// If you want to you can also move the text here
text.relocate(10, 10);
});
i++;
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
root.setCenter(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Note: You can also eliminate the Platform.runlater(...) block by updating the messageProperty of the task inside call() then binding the textProperty of the Text to this property.
Example:
Pane pane = new Pane();
Text text = new Text("");
text.relocate(10, 10);
pane.getChildren().add(text);
Task<Void> task = new Task<Void>() {
{
updateMessage("Initial text");
}
#Override
public Void call() throws Exception {
int i = 0;
while (true) {
if (i > 4)
updateMessage("I is bigger than 4");
if (i > 10)
updateMessage("I is bigger than 10");
i++;
Thread.sleep(1000);
}
}
};
text.textProperty().bind(task.messageProperty());
Thread th = new Thread(task);
th.setDaemon(true);
th.start();

static way of getting the resource of a file

Update:
I want to have the media player static but it does not work if i make is static.
Please note that the reason i want mediaPlayer static is that i want to access it from other classes.(the line is commented.)
This is my code:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import java.net.URL;
public class Main extends Application {
static boolean isSoundOn = false;
static double soundVolume = .5;
MediaPlayer mediaPlayer = new MediaPlayer(new Media(Main.class.getResource("song.mp3").toString()));
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
mediaPlayer.play();
primaryStage.setTitle("duet by what");
// primaryStage.setFullScreen(true);
//Group gamePaused = new Group();
//Scene _gamePaused = new Scene(gamePaused, 1200, 700);
//Group gameOver = new Group();
//Scene _gameOver = new Scene(gameOver, 1200, 700);
//Group game = new Group();
//Scene _game = new Scene(game, 1200, 700);
GUI gui = new GUI();
primaryStage.setScene(gui.getMainMenu().getScene());
primaryStage.show();
}
}
class GUI {
private MainMenu mainMenu = new MainMenu();
public class MainMenu {
private Scene scene;
private MainMenu() {
VBox vBox = new VBox();
scene = new Scene(vBox, 400, 500);
scene.getStylesheets().add("stylesheet.css");
Label info = new Label(
"welcome the the what version\n" +
"of the well known Duet game!\n\n" +
"press \"I wanna play!\" to begin the game.\n\n" +
"please note that you can change\n" +
"the sound settings.");
info.setId("info");
vBox.getChildren().add(info);
Button startGame = new Button("i wanna play right now!");
startGame.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("game started!");
}
});
vBox.getChildren().add(startGame);
Label highScore = new Label("__highScore should be added here__");
highScore.setId("highScore");
vBox.getChildren().add(highScore);
Button quitGame = new Button("get me out of this game!");
quitGame.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("game quitted!");
}
});
vBox.getChildren().add(quitGame);
CheckBox soundOn = new CheckBox("soundOn?");
Tooltip tooltip = new Tooltip("if this box is checked, music will be played!");
tooltip.setFont(new Font("Arial", 16));
soundOn.setTooltip(tooltip);
soundOn.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) {
Main.isSoundOn = soundOn.isSelected();
System.out.println(Main.isSoundOn);
}
});
vBox.getChildren().add(soundOn);
HBox changeVolumeWrapper = new HBox();
changeVolumeWrapper.setId("hBox");
Label sliderLabel = new Label("sound volume: ");
changeVolumeWrapper.getChildren().add(sliderLabel);
Slider soundVolume = new Slider(0, 1, .5);
soundVolume.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
Main.soundVolume = new_val.doubleValue();
//Main.mediaPlayer.setVolume(Main.soundVolume); here is why i need media player static.
System.out.printf("%.2f\n", Main.soundVolume);
}
});
changeVolumeWrapper.getChildren().add(soundVolume);
vBox.getChildren().add(changeVolumeWrapper);
}
public Scene getScene() {
return scene;
}
}
public MainMenu getMainMenu() {
return mainMenu;
}
}
Any other fixes to my code will be appreciated.
By the way, these are the errors i get:
Exception in thread "Thread-0" java.lang.IllegalStateException:
Toolkit not initialized at
com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at
com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at javafx.application.Platform.runLater(Platform.java:83) at
javafx.scene.media.Media$_MetadataListener.onMetadata(Media.java:541)
at
com.sun.media.jfxmediaimpl.MetadataParserImpl.done(MetadataParserImpl.java:120)
at
com.sun.media.jfxmediaimpl.platform.java.ID3MetadataParser.parse(ID3MetadataParser.java:237)
at
com.sun.media.jfxmediaimpl.MetadataParserImpl.run(MetadataParserImpl.java:103)
Exception in thread "main" java.lang.ExceptionInInitializerError at
java.lang.Class.forName0(Native Method) at
java.lang.Class.forName(Class.java:264) at
com.intellij.rt.execution.application.AppMain.main(AppMain.java:122)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
at
com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at
com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at javafx.application.Platform.runLater(Platform.java:83) at
javafx.scene.media.MediaPlayer.init(MediaPlayer.java:515) at
javafx.scene.media.MediaPlayer.(MediaPlayer.java:414) at
Main.(Main.java:22) ... 3 more
Calling getClass() without an object for context is interpreted the same as any other instance method: this.getClass().
In a static context, you can reference the class with ClassName.class; i.e. you can do
static URL resource = Main.class.getResource("a.mp3");
However, it is not at all clear in this scenario why you would want these variables to be static; only one instance of an Application subclass should ever be created per JVM instance, and these are inherently properties of that instance.
In the specific example in your (updated) question, I would define a separate class encapsulating the MediaPlayer and the other properties you currently make static. Note that MediaPlayer itself defines a volume property and a muted property. So you could do:
public class SoundPlayer {
private final MediaPlayer mediaPlayer ;
public SoundPlayer(URL url) {
this.mediaPlayer = new MediaPlayer(new Media(url));
}
public void play() {
mediaPlayer.play();
}
public double getVolume() {
return mediaPlayer.getVolume();
}
public void setVolume(double volume) {
mediaPlayer.setVolume(volume);
}
public boolean isSoundOn() {
return ! mediaPlayer.isMuted();
}
public void setSoundOn(boolean soundOn) {
mediaPlayer.setMuted(! soundOn);
}
}
Now your Main class can be:
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
SoundPlayer soundPlayer = new SoundPlayer(getClass().getResource("song.mp3"));
soundPlayer.play();
primaryStage.setTitle("duet by Aran Mohyeddin");
GUI gui = new GUI(soundPlayer);
primaryStage.setScene(gui.getMainMenu().getScene());
primaryStage.show();
}
}
and update your GUI and MainMenu classes to have a reference to a SoundPlayer:
public class MainMenu {
private Scene scene;
private final SoundPlayer soundPlayer ;
private MainMenu(SoundPlayer soundPlayer) {
this.soundPlayer = soundPlayer ;
// existing code omitted...
CheckBox soundOn = new CheckBox("soundOn?");
Tooltip tooltip = new Tooltip("if this box is checked, music will be played!");
tooltip.setFont(new Font("Arial", 16));
soundOn.setTooltip(tooltip);
soundOn.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) {
soundPlayer.setSoundOn(new_val);
}
});
// ...
Slider soundVolume = new Slider(0, 1, .5);
soundVolume.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
soundPlayer.setVolumn(new_val.doubleValue());
System.out.printf("%.2f\n", Main.soundVolume);
}
});
changeVolumeWrapper.getChildren().add(soundVolume);
vBox.getChildren().add(changeVolumeWrapper);
}
public Scene getScene() {
return scene;
}
}
public MainMenu getMainMenu() {
return mainMenu;
}
}
Also note that if you expose the actual property objects from SoundPlayer, for example:
public class SoundPlayer {
private final MediaPlayer mediaPlayer ;
// ...
public DoubleProperty volumeProperty() {
return mediaPlayer.volumeProperty();
}
// ...
}
then you can simplify some of your code:
Slider soundVolume = new Slider(0, 1, .5);
// instead of the listener, just do:
soundPlayer.volumeProperty().bindBidirectional(soundVolume.valueProperty());
(Converting the mutedProperty to a soundOnProperty is a little less elegant.)

Categories