JAVAFX how to update GUI elements (nodes) - java

This question is already asked but i copuldnt udnerstand it. Imagine that. I have a program with 2 scenes. First scene1 is opened which is the scene for connection to database. There is a label called Status which should turn from "Disconnected" to "Connected" when the connection is established(by clicking the COnnect button). So i made a function to take the button "Connect" onClick event. This function is declared and defined inside a controller class (i am using fmxl designing with scene builder). So basicly i want to change the status to "COnnected" (status.setText("Connected")) from the connection function(method) which is inside the controller class. However when I do that, the text isn't changed instantly after the connection is established, but it changes when the scene is about to close and i am about to change the scene to the new one... I read on the internet and i saw that i should use Platform.runLater and threading so i tried:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
Thread thrd = new Thread() {
public void run() {
Platform.runLater(new Runnable() {
#Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
}});
}
};
thrd.start();
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
changeSC is the function that is executed when Connect button is clicked. This is my old version which also doesnt work:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
The problem is with the text which should change to "Connected". It changes just when my scene is about to be switched....

You need to use a Task, if you have some long running operation. Otherwise when that operation is called from the JavaFX Application Thread it would block the GUI.
If you want to update the GUI from the Task you have to use Platform.runlater, which will run the code on the JavaFX Application Thread:
Platform.runlater
Updates to the Nodes of your GUI have always to be performed on the JavaFx Thread.
When you update status inside the Listener of button it should work.
button.setOnAction(evt -> {
dbConnect();
status.setText("Connected");
// ...
});
If dbConnect() takes some time, you can use a Task:
Task<Void> longRunningTask = new Task<Void>() {
#Override
protected Void call() throws Exception {
dbConnect();
Platform.runLater(() -> status.setText("Connected"));
return null;
}
};
button.setOnAction(e -> {
new Thread(longRunningTask).start();
});

Since you are connecting to a database, you should put this code to run in background using Task or a Service to keep the GUI Thread responding to the user inputs. Just remember that only in the GUI Thread you can update the view state (changing the value of a text in your case). You can use a java Thread and use Platform.runLater which means that the code inside is schedule to be precessed by the GUI Thread but in your case you are using in the wrong way. First the logic to connect to the database should be inside the method run of the thread and once the method finish, set the value of the text and do whatever you want after. Also you'll want to show the new Scene when all the process has been finished to get a chance to the user to see the change in the text. You can change your code in this way:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Thread thrd = new Thread() {
public void run() {
dbConnect();
Platform.runLater(new Runnable() {
#Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
}
});
}
};
thrd.start();
}
If you choose use a Task you dont have to deal with Platform.runLater explicitly. You only need to create a task (an implementation of the class Task), wrap this inside a java Thread, start it and the set a handler for the different events (eg: setOnSucceeded). This is your code using Task:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Task<Void> task = new Task<Void>(){
#Overrdie
protected Void call() {
dbConnect();
return null;
}
};
//start Task
Thread t = new Thread(task);
t.setDaemon(true); // thread will not prevent application shutdown
t.start();
task.setOnSucceeded(event -> {
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
});
}

ok i fixed it by setting task.SetonFailed() :)

Related

change color of disableProperty() function / how disable clicking on Stage

Hello it's my first ask on stack. I got problem with disablebleProperty() function. It make my Stage dark grey and it's looking terrible. How can I disable clicking on my stage when some Task is running like in the following code:
buttonName.setOnAction((event) -> {
Task task = new Task() {
#Override
protected Integer call() throws Exception {
try {
buttonName.getScene().setCursor(Cursor.WAIT);
buttonName.getScene().getRoot().disableProperty().bind(primaryStage.getScene().cursorProperty().isEqualTo(Cursor.WAIT));
anotherFunction();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
buttonName.getScene().setCursor(Cursor.DEFAULT);
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
});
You should not update the ui from a thread other than the JavaFX application thread. Since Task.call is run on a seperate thread, you shouldn't run the code there. Also binding to the curser property instead of also calling setDisable(false) seems like a bad idea, especially since you never unbind the property.
Use the event handler for onSucceeded for handling the successful completion of the task. (onFailed and onCanceled are available for different ways your task could finish, which won't happen in the code snippet you posted.)
buttonName.setOnAction((event) -> {
buttonName.getScene().setCursor(Cursor.WAIT);
buttonName.setDisable(true);
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
try {
anotherFunction();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
};
task.setOnSucceeded(evt -> {
buttonName.getScene().setCursor(Cursor.DEFAULT);
buttonName.setDisable(true);
});
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
});
To disable other controls in the scene too, you could instead disable/reenable the root of the scene:
buttonName.getScene().getRoot().setDisable(newValue);
To prevent the grayed out look JavaFX applies on the disabled controls (setting opacity to 0.4) you could apply a different opacity with higher precedence e.g. by setting the opacity from code
buttonName.setOpacity(1);
or by applying a style using a custom stylesheet
style.css
*:disabled {
-fx-opacity: 1;
}
scene initialisation
scene.getStylesheets().add("style.css");

Multi threading in Java with Task only work once when onClick Button

Attached is the code snippet below. I am new to multi-threading. Attempted to do multi threading which sort of works. However, after I click the button the first time, the second time onwards would not "create the thread" and run my method anymore.
I have also experimented with implementing the Runnable interface, but it could not get my Anchorpane reference to load the snackbar and hence I used the task method instead. Appreciate your help!
#FXML
private AnchorPane anchorPane;
Thread thread;
#FXML
void onClickLoginButton(ActionEvent event) throws Exception {
thread = new Thread(task);
thread.start();
}
Task<Void> task = new Task<Void>() {
#Override
public Void call(){
//System.out.println("Thread running"+thread.getId());
try {
credential = login.login();
} catch (UnknownHostException u) {
Platform.runLater(() -> {
System.out.println("No wifi");
JFXSnackbar snackbar = new JFXSnackbar(anchorPane);
snackbar.show("Please check your internet connection", 3000);
//u.printStackTrace();
});
} catch (Exception e) {
//e.printStackTrace();
}
//System.out.println("Thread running"+thread.getId());
return null;
}
};
The reason why this runs only once has to do with how Task works in general
Per Task's documentation here: https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html
As with FutureTask, a Task is a one-shot class and cannot be reused.
See Service for a reusable Worker.
Thus, if you want to repeat the said process multiple times, using a Task is not a good choice. Check the recommended alternatives for workers and services in you want to achieve something like that.

Trying to use Task to update a JavaFX scene graph, getting errors on calls to event handlers outside the task object

I wrote a small folder/image browser in JavaFX. (Note - I'm using Java 8)
It currently displays folders by going through the folder's contents, finding any images, picking 4 of them at random, and then displaying those images along with the folder name.
If there are lots of folders with lots of images in a given parent folder, this process can take a fairly long time.
I'm trying to change the code to display each folder and its selected images one at a time as the method goes through them, rather than all at once after the method finishes running.
From what I've gotten out of looking into how to do this, the typical approach to something like this is to take your code and put it into a Task object, then run the task in a separate thread, using the Platform.runlater method to do object updates on the original FX Application thread, like:
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;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
However, the code I'd like to update contains two setOnMouseClicked events, imageView.setOnMouseClicked(this::folderImageClick) and myFolder.setOnMouseClicked(this::folderClick). that call handlers written outside of the task. In terms of the above example I guess it would be
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);
r.setOnMouseClicked(this::mouseEventHandler);
Platform.runLater(new Runnable() {
#Override public void run() {
group.getChildren().add(r);
}
});
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
When I do this, I get the errors
The method setOnMouseClicked(EventHandler<? super MouseEvent>) in the type Node is not applicable for the arguments (this::folderImageClick)
and
The type new Task<Void>(){} does not define folderImageClick(MouseEvent) that is applicable here
Is there way to fix the event handler calls so that they work inside of Task?
this points to the Task when used from a method of the task. Obviously there is no mouseEventHandler in your anonymus class. You need to use a reference to the outer class containing the method. Assuming the name of the class is OuterClass:
OuterClass.this::mouseEventHandler

Java method run only once the full code after a scheduler runnable shutdown using JavaFX

Im stuck programing a Tetris game trying to learn Java and JavaFX.
When I call the method the first time from method start it runs well, when I call it from the thread it only runs partially, and dont add a new node to the pane.
Here is part of the code, you can view it entirely in
#Override
public void start(Stage stage) {
...
stage.show();
newPieces();
}
public void newPieces() {
Pieces pieces = new Pieces();
piece = pieces.createPiece();
nextPiece = pieces.createPiece();
boolean add1 = vbox.getChildren().add(nextPiece);
boolean add2 = pane.getChildren().add(piece);
translateDown(piece);
}
public void translateDown(Group piece) {
scheduler.scheduleWithFixedDelay(new Runnable() {
#Override
public void run() {
boolean translate = piece.getTransforms().add(new Translate(0, 25));
...
...
newPieces();
scheduler.shutdown();
...
...
}, 0, 1, TimeUnit.SECONDS);
The method newPieces creates a Group of Rectangles.
Thanks!
Instead of using a executor to schedule the updates I recommend using a Timeline which executes a frame's event handler on the application thread. Running the updates on a application thread is important since modifying properies of nodes on a different thread is problematic:
Timeline timeline = new Timeline();
timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1), evt -> {
updateBoard(); // do things like moving a piece...
if (checkLoss()) {
// stop updates when game is lost
timeline.stop();
}
}));
// repeat indefinitely
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
You could also use timeline.pause() to pause the game and set the rate property to speed up the game...
If you want to access the javafx gui components you have to run the code on the javafx thread. The extra thread you start is not allowed to access the javafx elements. The exception thrown is probably uncatched.
Try Platform.runLater(Runnable runnable) with the code modifing the gui. This runs the specified runnable on the javafx thread at some time in the future.

javafx show progressindicator while loading [duplicate]

This question already has an answer here:
JavaFX indeterminate progress bar while doing a process
(1 answer)
Closed 7 years ago.
This is the main window of my application:
I want to be able to update the window, so i wrote a method reset which ist triggered by pressing F5:
public void reset() {
final ProgressIndicator pi = new ProgressIndicator(-1.0f);
final HBox hb = new HBox(pi);
final VBox vb = new VBox(hb);
hb.setAlignment(Pos.CENTER);
vb.setAlignment(Pos.CENTER);
scene.setRoot(vb);
//this.show();
}
if i run this it looks sth like this:
However, if I uncomment the line //this.show(); this will be executed:
public void show() {
final VBox box = API_CALL_AND_BUILDING_BOX_MAGIC_WHICH_I_STRIPPED;
gp.getChildren().clear();
gp.getChildren().add(box);
scene.setRoot(gp);
stage.sizeToScene();
}
and I will never be able to see my ProgressIndicator because the application will just hang until the APIcall is finished and the new content is loaded.
I tried a bit with Threads and Platform.runLater() but I cant get it to work. My goal is to display the ProgressIndicator until the APIcall is finished, the box is build and the scene gets given gp as the new root.
I hope its somewhat understandable what my goal and my problems are :)
You need to create another JVM thread dedicated to fetch data from the remote source. The main thread of your application must never block with network operations: in the keyboard event handler you just repaint the main window with the progress indicator and then wait for another event, that can be:
background operation finished (successful, unsuccessful)
user interrupts (mouse/keyboard)
a timeout expires
Pseudocode with ListenableFuture by Guava
// Initialize in ctor use MoreExecutors
private final ListeningExecutorService service;
// Keep to call .cancel()
private ListenableFuture<APIData> currentRequest;
// Run in the GUI thread
public void onRefreshEvent() {
showProgressIndicator();
currentRequest = service.submit(/* Some Callable<APIData> */);
Futures.addCallback(currentRequest , this, this);
}
// From FutureCallback<T>
public void onFailure(Throwable t) {} // TODO
public void onSuccess(APIData d) {} // TODO
// From Executor
public void execute(Runnable cmd) {
Platform.runLater(cmd);
}
Don't forget to consider socket connect and read timeout, as well as pooling HTTP connection! Also consider if the background task should check the interrupted flag

Categories