Reputation: 35
I am new to java as well as javaFx, I am trying to work on a project which needs to display some live incoming data on labels.
I bind my label to message of a service object which keep updating its message with the incoming data. However, the message is updating but the label turns to blank.
There are no errors pop up nor exceptions been caught. Can anyone point out what makes the label blank rather than updating along with the service.message and how to fix it? Thanks in advance.
Below is a simplified example of what I am trying to do, and in which the data source is replaced by a list of random numbers.
controller class: where I put the scheduledservice object in, and binding it to the label.
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.util.Duration;
public class Controller {
@FXML
Button startButton;
@FXML
Label updateLabel;
@FXML
void display(ActionEvent event) throws Exception{
Steaming steaming = new Steaming();
ScheduledService<Void> service = new ScheduledService<Void>() {
protected Task<Void> createTask() {
return new Task<Void>() {
protected Void call() {
// Call the method and update the message
updateMessage(steaming.processData());
return null; // Useful in case you want to return data, else null
}
};
}
};
service.setPeriod(Duration.seconds(1)); //Runs every 1 seconds
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent t) {
System.out.println("Message:" + service.messageProperty());
}
});
updateLabel.textProperty().bind(service.messageProperty());
service.start();
}
}
Streaming class contains a list, in which i generates 1000 random ints. the processData method will return and remove the 1st element in list.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Steaming{
List<Integer> numbers;
Steaming()
{
numbers = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
numbers.add(i);
}
Collections.shuffle(numbers);
}
public String processData (){
String s = "loading";
try {
s = this.numbers.get(0).toString();
numbers.remove(0);
} catch (Exception e) {
e.printStackTrace();
}
return s;
}
}
Main class, show the primary stage
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("/sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
FXML file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<GridPane alignment="center" hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<columnConstraints>
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<children>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<Button fx:id="startButton" mnemonicParsing="false" onAction="#display" text="Button">
<HBox.margin>
<Insets right="5.0" />
</HBox.margin>
</Button>
<Label fx:id="updateLabel" prefHeight="26.0" prefWidth="105.0" text="loading">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin></Label>
</children>
</HBox>
</children>
</GridPane>
Running results: Here are two Pics for both before service start and after.
Label shows "loading" before clicking the button
Label shows nothing after clicking the button
here's a part of the terminal result got from the program.
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 33]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 200]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 389]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 188]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 915]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 76]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 205]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 583]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 181]
Message:StringProperty [bean: sample.Controller$1@7db1e6b2, name: message, bound, value: 872]
I am new in this community as well, if there are anything that I done wrongly in the post or breach any of the rules here, please kindly point them out as well. Thanks a lot!
Upvotes: 3
Views: 265
Reputation: 18792
Adding to Slaw's answer:
Would using JavaFx
animation tools work for you ?
Steaming steaming = new Steaming();
Timeline timeline = new Timeline(
new KeyFrame(Duration.millis(1000),
t -> updateLabel.setText(steaming.processData()))
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
If steaming.processData()
is long and you want to keep it on a background thread, you can update gui like so:
Steaming steaming = new Steaming();
ScheduledService<Void> service = new ScheduledService<>() {
@Override
protected Task<Void> createTask() {
return new Task<>() {
@Override
protected Void call() {
String text = steaming.processData();
Platform.runLater(()-> updateLabel.setText(text));
return null;
}
};
}
};
service.setPeriod(Duration.seconds(1)); //Runs every 1 seconds
service.start();
Upvotes: 2
Reputation: 45806
When a ScheduledService
succeeds it reschedules itself for the next execution cycle. Part of this process is "resetting" some of the properties to their default values, including setting the message
property to ""
1. So what's happening is your service succeeds, restarts, and the message is set back to ""
too quickly for it to render in the Label
.
Since you're code does nothing but update the message, one solution is to return the result of streaming.processData()
instead. You would then bind to the lastValue
property of the ScheduledService
. It must be lastValue
instead of value
because the latter also gets cleared2 on a reset/restart.
ScheduledService<String> service = new ScheduledService<>() {
@Override
protected Task<String> createTask() {
return new Task<>() {
@Override
protected String call() throws Exception {
return streaming.processData();
}
};
}
};
updateLabel.textProperty().bind(service.lastValueProperty());
1. I was unable to find documentation about this, but the implementation of Service.reset()
shows this to be the case (ScheduledService
extends Service
). Even if reset()
didn't clear the properties, the process of starting the service includes binding the properties to the underlying Task
—which is newly created and doesn't have any of its properties set.
2. This behavior is documented.
Upvotes: 2