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();
}
Related
I'm using a timeline in JavaFX to do a countdown of a Label:
timeline.setCycleCount(6);
timeline.play();
And I want to return a value after the timeline has finished:
return true;
However, it seems that the value is getting returned immediately and the timeline runs parallel. How can I wait until the timeline has finished its countdown and then return the value without blocking the timeline?
EDIT:
To make it more clear, I already tried:
new Thread(() -> {
timeline.play();
}).start();
while(!finished){ // finished is set to true, when the countdown is <=0
}
return true;
(This solution doesn't update the countdown.)
EDIT 2:
Here is a minimal, complete and verifiable example:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountdownTest extends Application {
private Label CountdownLabel;
private int Ctime;
#Override
public void start(Stage primaryStage) {
CountdownLabel=new Label(Ctime+"");
StackPane root = new StackPane();
root.getChildren().add(CountdownLabel);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Countdown Test");
primaryStage.setScene(scene);
primaryStage.show();
Ctime=5;
if(myCountdown()){
CountdownLabel.setText("COUNTDOWN FINISHED");
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public boolean myCountdown(){
final Timeline timeline = new Timeline(
new KeyFrame(
Duration.millis(1000),
event -> {
CountdownLabel.setText(Ctime+"");
Ctime--;
}
)
);
timeline.setCycleCount(6);
timeline.play();
return true;
}
}
You can see that it first shows "COUNTDOWN FINISHED" and counts down to 0 instead of starting with the countdown and counting down to "COUNTDOWN FINISHED".
As a Timeline inherits from Animation, you can use setOnFinished to define an action to occur at the end of the timeline.
timeline.setCycleCount(6);
timeline.play();
timeline.setOnFinished(event -> countdownLabel.setText("COUNTDOWN FINISHED"));
If you really want to wait till the timeline finished, you can use a CountDownLatch or a Semaphore along with setOnFinished. Something like the following should work:
CountDownLatch latch = new CountDownLatch(1);
timeline.setCycleCount(6);
timeline.setOnFinished(event -> latch.countDown());
timeline.play();
latch.await();
return true;
You are trying to wait in the one thread for the result of the work in the another thread. That is what synchronisation was created for! E.g. java.util.concurrent.Semaphore:
public boolean waitForTimeline() {
Semaphore semaphore = new Semaphore(0);
System.out.println("starting timeline");
Timeline t = new Timeline();
t.getKeyFrames().add(new KeyFrame(Duration.seconds(2)));
t.setOnFinished((e)-> {
System.out.println("releasing semaphore");
semaphore.release();
});
t.play();
System.out.println("waiting for timeline to end");
try {
semaphore.acquire();
} catch (InterruptedException ex) {
ex.printStackTrace();
return false;
}
return true;
}
But note, that you can't run this method on "JavaFX Application Thread" as it will block UI updates. Run it on a separate thread:
new Thread(()-> {
System.out.println("returned from timeline with " + waitForTimeline());
}).start();
or, better, instead create a listener with the logic you do after return and call that listener from t.setOnFinished(). For your example, it will be:
public void myCountdown(Runnable onSuccess){
//...
timeline.setOnFinished((e)-> {
onSuccess.run();
});
}
and corresponding call:
myCountdown(()->{
CountdownLabel.setText("COUNTDOWN FINISHED");
});
Use Animation's setOnFinished in addition to the KeyFrame's setOnFinished that you have (a Timeline is an Animation). Iv'e modified your MCVE to show how to do that for your case:
public class CountdownTest extends Application {
private Label countdownLabel;
private int ctime = 5;
#Override
public void start(Stage primaryStage) {
countdownLabel = new Label();
StackPane root = new StackPane();
root.getChildren().add(countdownLabel);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Countdown Test");
primaryStage.setScene(scene);
primaryStage.show();
myCountdown();
}
public void myCountdown() {
final Timeline timeline = new Timeline(new KeyFrame(Duration.millis(1000),
event -> {
countdownLabel.setText(ctime + "");
ctime--;
}
));
timeline.setCycleCount(ctime + 1);
timeline.setOnFinished(e -> countdownLabel.setText("COUNTDOWN FINISHED"));
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
On each cycle, the KeyFrame's setOnFinished will be executed, reducing the count by 1. When the whole animation finishes (all of its cycles), the Timeline's setOnFinished will be executed.
Notes:
Use the ctime variable to calculate the number of cycles so that they match.
Use the Java naming conventions: identifiers (names) start with lowercase.
Indent your code properly so that it's easier to read.
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
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();
I'm looking for a way to hide a Pane for a short time (around 100ms) and then immediately show it again.
Right now I'm using a StackPane with two AnchorPanes on top, and on key press I remove the top pane. However, that doesn't seem to happen immediately and it takes way too long.
I also tried using CSS to make the top pane invisible, but that doesn't seem to do anything at all.
Here's some code of that:
pn_middle.setStyle("-fx-background-color: rgba(128, 128, 128, 0);");
try {
Thread.sleep(1000); //1 sec for testing
} catch (InterruptedException e) {
e.printStackTrace();
}
pn_middle.setStyle("-fx-background-color: rgba(128, 128, 128, 1);");
If you use JavaFX 8, here is a solution using a timer from ReactFX. Unlike #ItachiUchiha's solution, it does not create any new threads.
import java.time.Duration;
import org.reactfx.util.FxTimer;
button.setOnAction(event -> {
pane.setVisible(false);
FXTimer.runLater(Duration.ofMillis(1000), () -> pane.setVisible(false));
});
Use a Timer to clock the time for which you want to hide your Pane. Try the example out, it contains a StackPane which has a Pane, colored as PINK and a Button. On the click of the Button, the Pane is hidden for 1000ms
import java.util.Timer;
import java.util.TimerTask;
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.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HideAndShowPane extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
StackPane stackPane = new StackPane();
Button button = new Button("Click Me to hide Pane !");
Pane pane = new Pane();
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//Hide the Pane
pane.setVisible(false);
//Schedule the Visibility for 1000ms
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
//Run on UI thread
Platform.runLater(new Runnable() {
#Override
public void run() {
pane.setVisible(true);
}
});
}
}, 1000);
}
});
pane.setPrefSize(200, 200);
pane.setStyle("-fx-background-color : PINK");
stackPane.getChildren().addAll(pane, button);
Scene scene = new Scene(stackPane, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Using Task
You can also achieve this by using Task and Thread.sleep ad later binding the valueProperty of the Task with the visibleProperty of the Pane
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//Create a Task
Task<Boolean> task = new Task<Boolean>() {
#Override
protected Boolean call() throws Exception {
try {
//Invisible for 1000ms
Thread.sleep(1000);
}
catch (InterruptedException e) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
};
//Start the Task
new Thread(task).start();
//Bind the visibility with Task Value
pane.visibleProperty().bind(task.valueProperty());
}
});
Without creating any new Threads
Thanks to Tomas Mikula's answer, this can also be achieved without creating any new Thread. Using a combination of Timeline, KeyFrames and KeyValue
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
pane.setVisible(false);
Timeline timeline = new Timeline();
timeline.getKeyFrames().add(
new KeyFrame(Duration.millis(1000),
new KeyValue(pane.visibleProperty(), true)));
timeline.play();
}
});
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();
}
}