JavaFX: Interrupt loop then update UI in another thread - java

I know there are million questions about updating UI in javafx from differents thread, but in my case I am working with one scene only and I would like interrupt a loop while and then I start another thread that will update UI.
I'm testing the following code :
Class main :
public static void main(String[] args)
{
Application.launch(args);
}
#Override
public void start(Stage stage) {
stage.setTitle("My project");
Group root = new Group();
Scene scene = new Scene(root, 800,600,Color.LIGHTBLUE);
Label lab = new Label("This is a test");
root.getChildren().add(lab);
stage.setScene(scene);
stage.show();
GuiUpdate gu=new GuiUpdate(stage,root);
int i=0;
while(i<5)
{
synchronized (this) {
gu.getThread().start();
try {
wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
i++;
}
}
my class GuiUpdate, I have 2 button.
First button execute an action.
Second Button stop this Thread and set previous Scene property.
public GuiUpdate(Stage stage, Parent root){
this.root=new Group();
thread = new Thread(this);
oldRoot=root;
this.stage=stage;
}
#Override
public void run()
{
Runnable runner = new Runnable() {
#Override
public void run() {
System.out.println("position 3 : runner");
Button[] button=new Button[2];
button[0]=new Button("Action");
//Set action for button 0
button[1]=new Button("Back");
button[1].setOnAction(e -> {
notify();
stage.getScene().setRoot(oldRoot);
getThread().interrupt();
});
root.getChildren().addAll(oldRoot,button[0],button[1]);
stage.getScene().setRoot(root);
}
};
if (Platform.isFxApplicationThread()) {
System.out.println("Position 1");
runner.run();
} else {
System.out.println("Position 2");
Platform.runLater(runner);
}
}
public Thread getThread(){return thread;}
}
Problem :
When I run project, Platform.runLater(runner) seems not to be working and Application freeze because "main Thread" is waiting

Related

Updating JavaFX ProgressBar works unexpectedly, how? [duplicate]

This question already has an answer here:
Update JavaFX Live nodes outside Application Thread
(1 answer)
Closed 2 years ago.
I'm in the process of teaching myself JavaFX and I recently stumbled upon "Platform.runLater", which should help you update the UI from inside a time consuming thread.
If I try to set any node inside the task thread, it won't let me because it's not running on the Application thread. HOWEVER, setting the progress of a progressbar works fine. Why is this? I've compared examples of use of the "runLater", and it does what it says until it comes to a "ProgressBar" (i.e, can't set labels or other nodes).
This works, as expected:
#Override
public void start(Stage primaryStage) throws Exception {
ProgressBar progressBar = new ProgressBar(0);
HBox hBox = new HBox(progressBar);
Scene scene = new Scene(hBox);
primaryStage.setScene(scene);
primaryStage.show();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
Platform.runLater(()->{
progressBar.setProgress(progressBar.getProgress()+0.1);
});
}
}
});
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) {
launch(args);
}
And this too, for unknown reason:
#Override
public void start(Stage primaryStage) throws Exception {
ProgressBar progressBar = new ProgressBar(0);
HBox hBox = new HBox(progressBar);
Scene scene = new Scene(hBox);
primaryStage.setScene(scene);
primaryStage.show();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
progressBar.setProgress(progressBar.getProgress()+0.1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) {
launch(args);
}
Why is this? I've compared both documentation and numerous examples...
In javafx can only manipulate UI elements such as the progressBar from within the dedicated platform thread. In your first example you do exactly this:
Platform.runLater(() -> {
progressBar.setProgress(progressBar.getProgress()+0.1);
});
which will execute `progressBar.setProgress(progressBar.getProgress()+0.1);´ within the dedicated UI thread as soon it has free capacity (previous commands have been exectued on it).
In your second example you try manipulate it from your newly spawned thread which will lead to the Not on FX application thread ... exception.
Whenever you want to make changes on the UI elements within some new thread make sure you perform those within
Platform.runLater(() -> {
...
});

JavaFX : Use an Indeterminate Progressbar in a splashScreen

I have a splash screen :
I need to have the animation of the progress bar (Indeterminate) but it doesn't work.
It's maybe due to because my thread is running in my initilize methode.
public class splashscreenController implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
new SplashScreen().run();
}
class SplashScreen extends Task {
#Override
public Object call() {
Platform.runLater(new Runnable() {
#Override
public void run()
Parent root = null;
try {
Thread.sleep(3000);
root = FXMLLoader.load(getClass().getResource("../gui/NewUI.fxml"));
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
Stage stage = new Stage();
stage.initStyle(StageStyle.UNDECORATED);
assert root != null;
Scene scene = new Scene(root, 1280, 720);
stage.setScene(scene);
stage.show();
MainJavaFx.setPrimaryStage(stage);
((Stage) panParent.getScene().getWindow()).close();
}
});
return null;
}
}
}
There are 2 issues in your code:
new SplashScreen().run();
A Task does not provide functionality for running on a new thread. run is executed on the calling thread.
class SplashScreen extends Task {
#Override
public Object call() {
Platform.runLater(new Runnable() {
#Override
public void run() {
// placeholder for parts of your code
longRunningOperation();
guiUpdate();
}
});
return null;
}
}
Even if you execute this task on a seperate thread, the Runnable passed to Platfrom.runLater is executed on the JavaFX application thread and doing long-running operations from this runnable freezes the GUI.
Do all the long-running operations on the background thread instead and only do short updates using Platfrom.runLater.
new Thread(new SplashScreen()).start();
class SplashScreen extends Task {
#Override
public Object call() throws IOException, InterruptedException {
Thread.sleep(3000);
final Parent root = FXMLLoader.load(getClass().getResource("../gui/NewUI.fxml"));
Platform.runLater(new Runnable() {
#Override
public void run() {
Stage stage = new Stage();
stage.initStyle(StageStyle.UNDECORATED);
Scene scene = new Scene(root, 1280, 720);
stage.setScene(scene);
stage.show();
MainJavaFx.setPrimaryStage(stage);
((Stage) panParent.getScene().getWindow()).close();
}
});
return null;
}
}
Note that since you're not using the functionality provided by Task, you could simply implement Runnable with your class instead of inheriting from Task.

Loading JavaFX GUI in background

I'm currently trying to create a Splash Screen for my program since it takes some time to start up.
The problem is that it takes a while to create the GUI (creating dialogues, updating tables etc.). And I can't move the GUI creation to a background thread (like the "Task" class), since I'll get an "Not on FXApplication Thread" exception.
I tried using:
Platform.runLater(new Runnable() {
public void run() {
//create GUI
}
}
And the "call" method of a Task:
public class InitWorker extends Task<Void> {
private Model model;
private ViewJFX view;
public InitWorker(Model model) {
this.model = model;
}
#Override
protected Void call() throws Exception {
View view = new View();
Collection collection = new Collection();
//do stuff
}
}
When I wrote the program in Swing I could just display and update the Splash Screen on the EventDispatchThread, without any real concurreny. The code looked like this:
public void build() {
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Menus");
menuCreator = new MenuCreatorOld (model, this);
menuCreator.createMenu();
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE, "Creating Toolbar");
toolBar = menuCreator.createToolBar();
createWesternPanelToolBar();
shoppingPanel = new ShoppingListOld(model, this, collectionController, shoppingController, controller);
centerTabbedPane = new JTabbedPane();
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Collection");
collectionPanel = new CollectionOld(model, collectionController, this, controller);
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Wish List");
wishPanel = new WishListOld(model, this, collectionController, wishController, controller);
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Folders Table");
//and so on
}
public static void updateProgressBar(int progressValue, String text) {
System.out.println("Loading Bar Value:"+progressValue);
progressBar.setValue(progressValue);
loadingLabel.setText(text);
progressBar.setString(text);
}
Is there any way to create the GUI in the background while displaying a Splash Screen with a loading bar?
Edit:
I had a look at my code and was able to decrease the startup time by 5 seconds. Most of the dialogs pull data from the database when they are created. So I moved the creation of the dialogs into their getter methods. That resulted in an improvement of 3 seconds. But I would still like to know if there is in a way to create the GUI on a background thread.
Edit:
As suggested, I also tried using "RunLater" in a "Task".
This way I can create the GUI and display the SplashScreen, but I can't update the progress bar and progress label, since the GUI creation blocks the JavaFX application thread. The progress bar and label are only updated, after the GUI has been fully created.
Here's an example you guys can run (I removed the splash screen and only kept the progress bar and progress label):
public class InitWorker extends Task<Void> {
private static ProgressBar progressBar;
private static Label progressLabel;
private static double PROGRESS_MAX = 5;
private double loadingValue;
public InitWorker() {
loadingValue = 0;
}
#Override
protected void succeeded() {
System.out.println("Succeeded");
}
#Override
protected void failed() {
System.out.println("Failed");
}
#Override
protected Void call() throws Exception {
System.out.println("RUNNING");
Platform.runLater(new Runnable() {
public void run() {
displaySplashScreen();
for(int i=0; i<10; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
updateProgressBar(loadingValue++, "Label "+i);
Stage stage = new Stage();
Label label = new Label("Label " + i);
VBox panel = new VBox();
panel.getChildren().add(label);
Scene scene = new Scene(panel);
stage.setScene(scene);
stage.centerOnScreen();
stage.show();
}
// updateProgressBar(1, "Initializing...");
}});
return null;
}
public void updateProgressBar(double loadingValue, String text) {
progressBar.setProgress(loadingValue / PROGRESS_MAX);
progressLabel.setText(text);
}
public static void displaySplashScreen() {
Stage progressBarStage = new Stage();
progressBar = new ProgressBar();
Scene progressBarScene = new Scene(progressBar);
progressBarStage.setScene(progressBarScene);
Stage progressLabelStage = new Stage();
progressLabel = new Label("Loading...");
progressLabel.setPadding(new Insets(5));
progressLabel.setStyle("-fx-background-color: red");
Scene progressLabelScene = new Scene(progressLabel);
progressLabelStage.setScene(progressLabelScene);
double progressBarWidth = 500;
double progressBarHeight = 75;
//muss angezeigt werden, um sie abhängig von Größe zu positionieren
progressBarStage.show();
progressLabelStage.show();
//
progressBarStage.setWidth(progressBarWidth);
progressBarStage.setHeight(progressBarHeight);
progressBarStage.centerOnScreen();
progressBarStage.centerOnScreen();
progressLabelStage.setY(progressLabelStage.getY() + 25);
}
}
See Task documentation titled "A Task Which Modifies The Scene Graph", which provides an example:
final Group group = new Group();
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
for (int i=0; i<100; i++) {
if (isCancelled()) break;
final Rectangle r = new Rectangle(10, 10);
r.setX(10 * i);
Platform.runLater(new Runnable() {
#Override public void run() {
group.getChildren().add(r);
}
});
}
return null;
}
};
The above example add the rectangles to the scene graph via a 100 runLater calls. A more efficient way to do this would be to add the rectangles to a group not attached to the active scene graph, then only add the group to the active scene graph in the runLater call. For example:
final Group groupInSceneGraph = new Group();
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
final Group localGroup = new Group();
for (int i=0; i<100; i++) {
if (isCancelled()) break;
final Rectangle r = new Rectangle(10, 10);
r.setX(10 * i);
localGroup.getChildren().add(r);
}
Platform.runLater(new Runnable() {
#Override public void run() {
groupInSceneGraph.add(localGroup);
}
});
return null;
}
};
You can create and modify most scene graph objects off of the JavaFX application thread (including loading FXML), as long as the objects aren't attached to the active scene graph. By active scene graph I mean a scene graph which is currently attached as a scene to a displayed stage. (A complicated control such as a WebView may be an exception to this rule and may require creation on the JavaFX application thread).
You must only attach the scene graph objects created off of the JavaFX application thread to the active scene graph on the JavaFX application thread (for example using Platform.runLater()). And, you must work with them on the JavaFX application thread as long they continue to be attached to the active scene graph.

Thread communication: How to signal that a key was clicked? java.lang.IllegalMonitorStateException

i have a simple JavaFX stage with a TextField. What i want to do is: when user inserts letters into the TextField, i want to print "now" (just to look if it works). Im using a Thread because later i want to scan a dictonary to see, if the letters the user entered are part of words from the dictionary.
But i get: java.lang.IllegalMonitorStateException
Any ideas? I don't seem to understand the whole concept of Condition.await and Multithreading..
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DictionaryThreading extends Application {
private static Lock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public static void main(String[] args) {
launch();
}
private static class ScanWords implements Runnable{
#Override
public void run() {
lock.lock();
try{
while(true){
this.wait();
System.out.println("clicked");
}
} catch (Exception e){
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
#Override
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
new ScanWords().run();
TextField tf = new TextField("Please enter a word");
tf.setOnKeyPressed(e -> {});
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
There is no need to create a thread that does nothing other than wait for user events. The JavaFX framework already provides this for you (it is one of the fundamental pieces of functionality of any UI toolkit). All you need to do to respond to changes in the text in a text field is register a change listener with the text field's text property:
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
TextField tf = new TextField("Please enter a word");
tf.textProperty().addListener((obs, oldText, newText) -> {
System.out.println("text changed");
});
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
If the thing you need to do in response to the text changing takes a long time, then you should launch that process in a background thread in the listener on the text field. If you are searching something large, you probably want to cancel any existing search, so that you don't end up with a large number of searches all running concurrently. The JavaFX Service class provides the functionality you need for this:
public class SearchService extends Service<List<String>> {
// modify and access only on FX Application Thread:
private String searchString ;
#Override
protected Task<List<String>> createTask() {
final String s = searchString ;
return new Task<List<String>>() {
#Override
protected List<String> call() throws Exception {
List<String> matches = new ArrayList<>();
// do search for strings matching s
// be sure to check isCancelled() regularly
return matches ;
}
};
}
public String getSearchString() {
checkThread();
return searchString ;
}
public void setSearchString(String searchString) {
checkThread();
this.searchString = searchString ;
}
private void checkThread() {
if (! Platform.isFxApplicationThread()) {
throw new IllegalStateException("Not on FX Application Thread");
}
}
}
Then you can do
public void start(Stage primaryStage) throws Exception {
StackPane pane = new StackPane();
SearchService searchService = new SearchService();
searchService.setOnSucceeded(e -> {
List<String> matches = searchService.getValue();
// do whatever you need with search results...
// this is called on FX application thread
});
TextField tf = new TextField("Please enter a word");
tf.textProperty().addListener((obs, oldText, newText) -> {
searchService.cancel();
searchService.setSearchText(newText);
searchService.restart();
});
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
I don't use JavaFX, but I think you need to use EventListener. Try to use TextListener or InputMethodListener. For example:
StackPane pane = new StackPane();
TextField tf = new TextField("Please enter a word");
tf.addTextListener(e -> System.out.println("Pushed"));
pane.getChildren().add(tf);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
wait method should be executed within synchronized block:
try{
while(true){
synchronized(this){
this.wait();
System.out.println("clicked");
}
}
} catch (Exception e){
e.printStackTrace();
} finally{
lock.unlock();
}

Switching fxml scenes with setScene hangs on Mac

We have a JDialog that contains a single stage with multiple scenes. Each scene has a next button. When the user clicks "next" we call stage.setScene. This works fine on Windows and Linux, but on Mac, setScene never returns. The application hangs, and it appears to be a thread deadlock. Here is a sample app that reproduces the problem. We have also tried various java 7 builds and java8 pre-release. We think we have found a couple workarounds, but we would like to understand why this code has problems on Mac. Sample app with bug:
public class SampleFxInsideSwing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new SampleFxInsideSwing().initFX();
}
});
}
private void initFX()
{
JDialog dialog = new JDialog();
dialog.setSize(new Dimension(500,500));
final JFXPanel stage = new JFXPanel();
dialog.add(stage);
Platform.runLater(new Runnable()
{
#Override
public void run()
{
Button nextButton = new Button("Next");
nextButton.setOnAction(new EventHandler<ActionEvent>()
{
//#Override
public void handle(ActionEvent event)
{
try
{
System.out.println("Clicked");
AnchorPane parent = FXMLLoader.load(SampleFxInsideSwing.class.getResource("SampleFxml.fxml"));
Scene secondScene = new Scene(parent);
stage.setScene(secondScene);
System.out.println("Displaying second scene!");
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
Group group = new Group();
group.getChildren().add(nextButton);
Scene scene1 = new Scene(group);
stage.setScene(scene1);
}
});
dialog.setVisible(true);
}
}

Categories

Resources