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);
}
}
Related
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
The application reacts on actions which occur on gamepad. When button is pressed something happens on UI. But I ran at the issue with app hangs up or "java.lang.IllegalStateException: Not on FX application thread" exception.
In order to fix it I tried the following approaches: Platform.runLater() and Task usage. But it didn't help.
Here is the problem code:
public class GamepadUI extends Application{
private static final int WIDTH = 300;
private static final int HEIGHT = 213;
private Pane root = new Pane();
private ImageView iv1 = new ImageView();
private boolean isXPressed = false;
#Override
public void start(Stage stage) throws Exception {
initGUI(root);
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public void pressBtn() {
if(!isXPressed) {
iv1.setVisible(true);
isXPressed = true;
}
}
public void releaseBtn() {
if(isXPressed) {
iv1.setVisible(false);
isXPressed = false;
}
}
private void initGUI(final Pane root) {
Image image = new Image(Props.BUTTON);
iv1.setImage(image);
iv1.setLayoutX(198);
iv1.setLayoutY(48);
iv1.setVisible(false);
root.getChildren().add(iv1);
runTask();
}
public void runTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
};
new Thread(task).start();
}
public static void main(String[] args) {
launch(args);
}
public void initStubGamepad() {
Random rnd = new Random();
try {
while (true) {
if (rnd.nextInt(30) == 3) {
pressBtn();
} else if (rnd.nextInt(30) == 7) {
releaseBtn();
}
}
} catch (Exception ex) {
System.out.println("Exception: " + ex);
}
}
}
initStubGamepad() emulates gamepad buttons activity polling. When user presses any button (rnd.nextInt(30) == 3) - an image appears on the UI. When user releases that button (rnd.nextInt(30) == 7) - an image disappears from the UI.
In case above java.lang.IllegalStateException: Not on FX application thread occurs. If you change runTask() to something like this:
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
Then app will hang or even main UI won't appear at all, but gamepad activity continues.
What I want is just to show/hide different images when some activity is detected on gamepad (btw, there's no way to monitor gamepad activity except for gamepad polling in an infinite loop). What did I wrong
Explanation
In the first scenario, when you are using
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
}
Inside initStubGamepad(), which is running on a Task, you are trying to update the UI components inside pressBtn() and releaseBtn() methods, which is why you are facing a
java.lang.IllegalStateException: Not on FX application thread
because all the UI updates must occur on the JavaFX thread
In the second scenario, when you are using
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
the UI doesnt appear, because you have an infinite loop inside the initStubGamepad(), which puts the JavaFX application thread run on an infinite loop
Solution
By the time you have reach here, you must have already found the solution. In case you haven't, try try to put the update the Javafx components on the UI thread. So, instead of calling initStubGamepad() inside Platform.runLater, try calling pressBtn() and releaseBtn() inside it.
Try using
while (true) {
if (rnd.nextInt(30) == 3) {
Platform.runLater(() -> pressBtn());
} else if (rnd.nextInt(30) == 7) {
Platform.runLater(() -> releaseBtn());
}
}
or you may also use
public void pressBtn() {
if(!isXPressed) {
Platform.runLater(() -> iv1.setVisible(true));
isXPressed = true;
}
}
How can we update multiple controls from a single Service. Right now there is only one single updateMessage() in Service, whose value can be bound to just one control and hence update just that. How can we update values for multiple controls ?
My instance of Service Class:
//run a background thread
threadTimeChecker = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
while (!isDone) {
DataHelper.setCurrentDate(LocalDate.now());
if(!DataHelper.getOldDate().equals(DataHelper.getCurrentDate())) {
DataHelper.setIntIndex(DataHelper.getIntIndex()+1);
DataHelper.setOldDate(DataHelper.getCurrentDate());
DataHelper.saveData();
System.out.println("Saved!");
}
//Thread.currentThread().sleep(2000);
updateMessage(wordString.getValue());
}
return null;
}
};
}
};
threadTimeChecker.restart();
//bind string properties to labels
word.textProperty().bind(threadTimeChecker.messageProperty());
This only updates one message i.e. I can only bind one label. Is there any way I can update multiple messages from the same thread so I can bind multiple labels in my UI?
EDITED - More Information according to comments
My runnable is:
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
//loop run until the program is closed
while (!isDone) {
DataHelper.setCurrentDate(LocalDate.now());
if (!DataHelper.getOldDate().equals(DataHelper.getCurrentDate())) {
Platform.runLater(new Runnable(){
#Override
public void run() {
DataHelper.setIntIndex(DataHelper.getIntIndex()+1);
}
});
DataHelper.setOldDate(DataHelper.getCurrentDate());
DataHelper.saveData();
}
Thread.currentThread().sleep(5000);
}
return null;
}
};
}
I run a thread to change DataHelper.IntIndex which invokes a listener that changes the 'String Property' as per the index like:
//listener to detect change in index and assign strings of word,meaning, and sentence, accordingly
DataHelper.intIndexProperty().addListener(
(v, oldValue, newValue) -> {
wordString.setValue(DataHelper.getListOfWords().get((int) newValue).get("word"));
meaningString.setValue(DataHelper.getListOfWords().get((int) newValue).get("meaning"));
sentenceString.setValue(DataHelper.getListOfWords().get((int) newValue).get("sentence"));
System.out.print("kjbmmj");
}
);
And I have used these 'String Properties' to bind to three different labels correspondingly like:
//bind string properties to labels
word.textProperty().bind(wordString);
meaning.textProperty().bind(meaningString);
sentence.textProperty().bind(sentenceString);
Now what I want to do is to use more JavaFX inclined updateMessage to achieve the same.
Instead of updating multiple messages, we just need to update a single instance of DataHelper. DataHelper has contents which will update multiple labels. For instance, let us consider we have the following labels which we want to update after each call of service :
wordLabel
meaningLabel
sentenceLabel
To keep things simple, let us consider that you've a class DataHelper which has three properties word, meaning and sentence.
private class DataHelper {
public DataHelper(String word, String meaning, String sentence) {
this.word.setValue(word);
this.meaning.setValue(meaning);
this.sentence.setValue(sentence);
}
StringProperty word = new SimpleStringProperty();
StringProperty meaning = new SimpleStringProperty();
StringProperty sentence = new SimpleStringProperty();
// setters and getters
}
We call the service for some background task and whenever the service is done with the background task it can return us the updated DataHelper.
Service<DataHelper> service = new Service<DataHelper>() {
#Override
protected Task<DataHelper> createTask() {
return new Task<DataHelper>() {
#Override
protected DataHelper call() throws Exception {
i.incrementAndGet(); // Don't worry about i here
return new DataHelper("Word " + i, "Meaning " + i, "Sentence " + i);
}
};
}
};
Now, every time we call the server we get an updated DataHelper which we want to show on the label(s).
To approach this, we declare a variable dataHelper and bind its properties to the textProperty() of various labels :
DataHelper dataHelper = new DataHelper("Word", "Meaning", "Sentence");
wordLabel.textProperty().bind(dataHelper.wordProperty());
meaningLabel.textProperty().bind(dataHelper.meaningProperty());
sentenceLabel.textProperty().bind(dataHelper.sentenceProperty());
Now you must be wondering, how will we update dataHelper, right? Well, that's the easy part. This can be can taken care in setOnSucceeded() of Service, where getValue() will return us a new instance of DataHelper with updated values.
service.setOnSucceeded(event -> {
dataHelper.setWord(service.getValue().getWord());
dataHelper.setMeaning(service.getValue().getMeaning());
dataHelper.setSentence(service.getValue().getSentence());
});
Complete MCVE :
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.concurrent.atomic.AtomicInteger;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
// Properties
DataHelper dataHelper = new DataHelper("Word", "Meaning", "Sentence");
AtomicInteger i = new AtomicInteger(0);
// UI elements
Label wordLabel = new Label();
Label meaningLabel = new Label();
Label sentenceLabel = new Label();
Button startService = new Button("Start");
Service<DataHelper> service = new Service<DataHelper>() {
#Override
protected Task<DataHelper> createTask() {
return new Task<DataHelper>() {
#Override
protected DataHelper call() throws Exception {
i.incrementAndGet();
return new DataHelper("Word " + i, "Meaning " + i, "Sentence " + i);
}
};
}
};
startService.setOnAction(e -> {
if(service.getState().equals(Worker.State.READY) || service.getState().equals(Worker.State.SUCCEEDED)) {
service.restart();
}
});
service.setOnSucceeded(event -> {
dataHelper.setWord(service.getValue().getWord());
dataHelper.setMeaning(service.getValue().getMeaning());
dataHelper.setSentence(service.getValue().getSentence());
});
wordLabel.textProperty().bind(dataHelper.wordProperty());
meaningLabel.textProperty().bind(dataHelper.meaningProperty());
sentenceLabel.textProperty().bind(dataHelper.sentenceProperty());
VBox box = new VBox(10, wordLabel, meaningLabel, sentenceLabel, startService);
box.setAlignment(Pos.CENTER);
Scene scene = new Scene(box, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
private class DataHelper {
StringProperty word = new SimpleStringProperty();
StringProperty meaning = new SimpleStringProperty();
StringProperty sentence = new SimpleStringProperty();
public DataHelper(String word, String meaning, String sentence) {
this.word.setValue(word);
this.meaning.setValue(meaning);
this.sentence.setValue(sentence);
}
public String getMeaning() {
return meaning.get();
}
public StringProperty meaningProperty() {
return meaning;
}
public void setMeaning(String meaning) {
this.meaning.set(meaning);
}
public String getSentence() {
return sentence.get();
}
public StringProperty sentenceProperty() {
return sentence;
}
public void setSentence(String sentence) {
this.sentence.set(sentence);
}
public String getWord() {
return word.get();
}
public StringProperty wordProperty() {
return word;
}
public void setWord(String word) {
this.word.set(word);
}
}
public static void main(String[] args) {
launch(args);
}
}
I'm trying to make a generic class that I can use for future projects. It just makes a simple javafx browser. The issue I'm having is that I want to be able to change some of the properties dynamically (on instantiation). I added some simple setters hoping it would to the job, but they do not work. Is there a way to change the variables after start() has been executed?
Class code:
package rob.rushton;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class RushBrowser extends Application {
public RushBrowser() {}
private String url = "www.google.com";
private final String fullUrl = "http://" + url;
String title = "Simple Browser";
private int height = 750;
private int width = 750;
public void openBrowser() {
launch();
}
public void setURL(String u) {
url = u;
}
public void setHeightWidth(int h, int w) {
height = h;
width = w;
}
public void setTitle(String t) {
title = t;
}
#Override
public void start(Stage stage) {
stage.setTitle(this.title);
BorderPane pane = new BorderPane();
Scene scene = new Scene(pane, width, height);
WebView browser = new WebView();
WebEngine engine = browser.getEngine();
engine.load(fullUrl);
pane.setCenter(browser);
stage.setScene(scene);
stage.show();
}
}
And I was trying to run it like this:
package rushtest;
import rob.rushton.RushBrowser;
public class RushTest {
public static void main(String[] args) {
RushBrowser rush = new RushBrowser();
rush.setTitle("Test Title");
rush.setURL("www.github.com");
rush.setHeightWidth(1000, 1000);
rush.openBrowser();
}
}
EDIT: (8/9/15) None of the listed suggestions below have worked :( The problem is that I do not know how to access the application thread that is started by launch()
You should either make those properties true JavaFX properties, or update their setters delegate to the actual UI objects. Some rough code - only the relevant parts shown:
Case of true JavaFX properties:
public class RushBrowser extends Application {
...
private StringProperty titleProperty = new SimpleStringProperty("Simple Browser");
...
public void setTitle(String t) {
titleProperty.set(t);
}
#Override
public void start(Stage stage) {
stage.titleProperty().bind(this.titleProperty);
...
}
}
Case of delegation - you also have to keep a reference of the Stage:
public class RushBrowser extends Application {
...
private Stage primaryStage;
// no need to keep the title member variable
...
public void setTitle(String t) {
primaryStage.setTitle(t);
}
#Override
public void start(Stage stage) {
this.primaryStage = stage;
...
}
}
EDIT
As per the comment from James_D, there is another problem: the main method has no reference to the instance created by Application.launch(). So:
If you want to customize the parameters of your application before it starts, you can override Application.init():
public class SpecialRushBrowser extends RushBrowser {
public void init() {
this.setTitle("Test Title");
...
}
}
Or from the test code:
public class RushTest {
static class TestRushBrowser extends RushBrowser {
public void init() {
super.init(); // just in case
this.setTitle("Test Title");
...
}
}
public static void main(String[] args) {
TestRushBrowser.launch();
}
}
If you do not need to modify these parameters later, you can leave your code as is (i.e. no JavaFX properties are required). Otherwise, apply the changes mentioned above.
If you want to change the parameters after the application has started, you need to provide a reference to the actual instance of RushBrowser created by Application.launch() to the code that will execute the changes. A simple but dirty way is with a global variable:
public class RushBrowser extends Application {
public static RushBrowser INSTANCE;
public void init() {
INSTANCE = this;
}
...
}
And then from any code that runs after launch():
RushBrowser.INSTANCE.setTitle(...);
...
As global state is generally dangerous, you might want to try with a dependency injection framework, if the application gets more complex. Even with DI though it can get tricky because the main class is still created from JavaFX, outside the DI framework - but that's another story.
Again you need to apply the changes above the EDIT.
The reason the code you posted doesn't work is that the (static) launch(...) method creates a new instance of the Application subclass for you, and then calls its start(...) method. So the instance for which you call all the setXXX(...) methods is not the instance whose start(...) method is invoked.
You're defining the reusable part in the wrong place: an Application subclass is inherently not reusable. You should regard the start() method in your Application subclass as the equivalent of the main() method in a regular JavaFX application.
So:
public class RushBrowser {
private final BorderPane view ;
private String url = "www.google.com";
private final String fullUrl = "http://" + url;
private String title = "Simple Browser";
private int height = 750;
private int width = 750;
private WebEngine engine ;
public RushBrowser() {
WebView browser = new WebView();
view = new BorderPane(browser);
engine = browser.getEngine();
}
public Node getView() {
return view ;
}
public void show(Stage stage) {
Scene scene = new Scene(view, width, height);
stage.setScene(scene);
stage.show();
}
public void show() {
show(new Stage());
}
public void setURL(String u) {
url = u;
}
public void setHeightWidth(int h, int w) {
height = h;
width = w;
view.setPrefSize(w, h);
}
public void setTitle(String t) {
title = t;
Scene scene = view.getScene();
if (scene != null) {
Window window = scene.getWindow();
if (window instanceof Stage) {
((Stage)window).setTitle(title);
}
}
}
}
and then you test it with an Application subclass:
public class RushBrowserTest extends Application {
#Override
public void start(Stage primaryStage) {
RushBrowser rush = new RushBrowser();
rush.setTitle("Test Title");
rush.setURL("www.github.com");
rush.setHeightWidth(1000, 1000);
rush.show(primaryStage);
}
public static void main(String[] args) { launch(args); }
}
and of course you can use it elsewhere. As an arbitrary example:
TabPane tabPane = ... ;
RushBrowser rush = new RushBrowser();
rush.setURL("www.github.com");
Tab tab = new Tab();
tab.setContent(rush.getView());
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();
}
}