I am a student and learning JavaFX since a month.
I am developing a application where I want a service to repeatedly start again after its execution of the task. For this I have come to know that 'ScheduledService' is used.
So can anybody please explain the use of scheduledservice with simple example and also how it differs from the 'Service' in JavaFX. Thanks ;)
EDIT : How can I define that this ScheduledService named DataThread should be restarted every 5 seconds ?
public class DataThread extends ScheduledService<Void>
{
#Override
public Task<Void> createTask() {
return new Task<Void>() {
#Override
public Void call() throws Exception {
for(i=0;i<10;i++)
{
System.out.println(""+i);
}
return null;
}
};
}
}
Considering you have a sound knowledge of Service class. ScheduledService is just a Service with a Scheduling functionality.
From the docs
The ScheduledService is a Service which will automatically restart itself after a successful execution, and under some conditions will restart even in case of failure
So we can say it as,
Service -> Execute One Task
ScheduledService -> Execute Same Task at regular intervals
A very simple example of Scheduled Service is the TimerService, which counts the number of times the Service Task has been called. It is scheduled to call it every 1 second
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TimerServiceApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TimerService service = new TimerService();
AtomicInteger count = new AtomicInteger(0);
service.setCount(count.get());
service.setPeriod(Duration.seconds(1));
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
System.out.println("Called : " + t.getSource().getValue()
+ " time(s)");
count.set((int) t.getSource().getValue());
}
});
service.start();
}
public static void main(String[] args) {
launch();
}
private static class TimerService extends ScheduledService<Integer> {
private IntegerProperty count = new SimpleIntegerProperty();
public final void setCount(Integer value) {
count.set(value);
}
public final Integer getCount() {
return count.get();
}
public final IntegerProperty countProperty() {
return count;
}
protected Task<Integer> createTask() {
return new Task<Integer>() {
protected Integer call() {
//Adds 1 to the count
count.set(getCount() + 1);
return getCount();
}
};
}
}
}
Related
I would like to ask what is the most elegant way to capture value change of CheckBoxTableCell in my TableView.
My goal is to save new value in DB which my example shows:
printedColumn.setCellValueFactory(f -> f.getValue().getPrintedProperty());
printedColumn.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer param) {
ProductFx productFx = addProductModel.getProductFxObservableList().get(param);
updateInDatabase(productFx);
return productFx.getPrintedProperty();
}
}));
This works fine, but I don't feel like it's the best way to achieve that. For other columns I follow this way:
#FXML
public void onEditPrice(TableColumn.CellEditEvent<ProductFx, Number> e) {
ProductFx productFx = e.getRowValue();
productFx.setPrice(e.getNewValue().doubleValue());
updateInDatabase(productFx);
}
fxml:
<TableColumn fx:id="priceColumn" onEditCommit="#onEditPrice" prefWidth="75.0" text="%addProductTable.price" />
Is it possible to do it in similar way with #FXML annotated method and fxml configuration? Maybe some other ideas?
I really find it hard to get the idea behind your question. I publish the code how I think it should be done. If it does not meet your requirements please elaborate on what you are exactly trying to achive.
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.Stage;
public class TableViewApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<Product> products = FXCollections.observableArrayList(product -> new ObservableValue[] {product.nameProperty()});
products.add(new Product(1l, "Machine1"));
products.add(new Product(2l, "Machine2"));
products.addListener((ListChangeListener<Product>) change -> {
while (change.next()) {
if (change.wasUpdated()) {
for (int i = change.getFrom(); i < change.getTo(); i++) {
updateInDb(change.getList().get(i));
}
}
}
});
TableView<Product> tableView = new TableView<>(products);
tableView.setEditable(true);
TableColumn<Product, Long> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(cellData -> cellData.getValue().idProperty().asObject());
TableColumn<Product, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setEditable(true);
tableView.getColumns().add(idColumn);
tableView.getColumns().add(nameColumn);
stage.setScene(new Scene(tableView));
stage.show();
}
private void updateInDb(Product product) {
System.out.println("Update " + product + " in db");
}
}
class Product {
private LongProperty id = new SimpleLongProperty();
private StringProperty name = new SimpleStringProperty();
public Product(long id, String name) {
this.id.set(id);
this.name.set(name);
}
public LongProperty idProperty() {
return id;
}
public long getId() {
return id.get();
}
public StringProperty nameProperty() {
return name;
}
public String getName() {
return name.get();
}
#Override
public String toString() {
return "Product[id=" + getId() + ", name=" + getName() + "]";
}
}
I do not understand in detail how you got your code working. I guess it is not working as intended by the design of the API. I can definitely answer if it is possible to do it with a simple FXML attribute of CheckBoxTableCell: No.
In case of CheckBoxTableCell
... it is not necessary that the cell enter its editing state (...). A side-effect of this is that the usual editing callbacks (such as on edit commit) will not be called. If you want to be notified of changes,
it is recommended to directly observe the boolean properties that are
manipulated by the CheckBox.
as stated by the javadoc.
If the class of printedProperty implements ObservableValue<Boolean>(as stated by your code) you should follow the cited doc and add a ChangeListener to it like
printedColumn.setCellValueFactory(f -> f.getValue().getPrintedProperty());
printedColumn.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer param) {
ProductFx productFx = addProductModel.getProductFxObservableList().get(param);
return productFx.getPrintedProperty();
}
}));
ObservableList<ProductFx> obs = addProductModel.getProductFxObservableList();
obs.addListener(new ListChangeListener<ProductFx>(){
#Override
public void onChanged(Change<? extends ProductFx> c) {
if(c.wasAdded()) {
for (ProductFx s:c.getAddedSubList()) {
s.getPrintedProperty().addListener(new ChangeListener<Boolean>() {
ProductFx localProductFx=s;
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
updateInDatabase(localProductFx);
}
});
}
}
}
});
But this is not elegant at all.
To your approach to solve the problem:
The Callback<Integer, ObservableValue<Boolean>>() you used is called each time when the displayed cell is updated. This happens especially when you are scrolling through a huge list, because TableView only keeps as many Cell instances as necessary to fill its view-port (doc). They are simply updated during scrolling and your code updates the database each time this happens, so you might run into performance problems for large datasets or slow databases.
PS: As far as I understand your code you do not follow the usual naming conventions for properties. This might lead to problems using reflecting classes like PropertyValueFactory.
I am performing some background task using this class
class Download extends Task{
protected Object call() throws Exception {
try {
updateMessage("Establishing Connection");
DownloadHelper downloadHelper = new DownloadHelper();
downloadHelper.performTask();
return null;
} catch (IOException | ParseException ex) {
logger.error(ExceptionUtils.getStackTrace(ex));
throw ex;
}
}
}
This Task in turn calls DownloadHelper to perform some task.
class DownloadHelper{
public DownloadHelper(){
}
public void performTask(){
----
----
}
}
Is there a way to update the status message of the Task API (updateMessage()) from the DownloadHelper class.?
The expedient approach is to pass a reference to the Download task as a parameter to the DownloadHelper constructor. To minimize coupling, you can instead pass a reference to your implementation of updateMessage() as a parameter of type Consumer, "an operation that accepts a single input argument and returns no result."
DownloadHelper helper = new DownloadHelper(this::updateMessage);
Your helper's implementation of performTask() can then ask the updater to accept() messages as needed.
Consumer<String> updater;
public DownloadHelper(Consumer<String> updater) {
this.updater = updater;
}
public void performTask() {
updater.accept("Helper message");
}
A related example is seen here.
import java.util.function.Consumer;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/45708923/230513
*/
public class MessageTest extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("MessageTest");
StackPane root = new StackPane();
Label label = new Label();
root.getChildren().add(label);
Scene scene = new Scene(root, 320, 120);
primaryStage.setScene(scene);
primaryStage.show();
Download task = new Download();
task.messageProperty().addListener((Observable o) -> {
label.setText(task.getMessage());
});
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
private static class Download extends Task<String> {
#Override
protected String call() throws Exception {
updateMessage("Establishing connection");
DownloadHelper helper = new DownloadHelper(this::updateMessage);
helper.performTask();
return "MessageTest";
}
#Override
protected void updateMessage(String message) {
super.updateMessage(message);
}
}
private static class DownloadHelper {
Consumer<String> updater;
public DownloadHelper(Consumer<String> updater) {
this.updater = updater;
}
public void performTask() {
updater.accept("Helper message");
}
}
public static void main(String[] args) {
launch(args);
}
}
Look at this example:
package sample;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
public class Main extends Application {
//NOTICE: This is class in **other file** (here is just for example)
private static class MyService extends Service {
#Override
protected Task createTask() {
return new Task() {
#Override
protected Object call() throws Exception {
System.out.println("Service: START");
while(true) {
System.out.println("Service: ITERATION");
// Thread.sleep(3000); // This raise InterruptedException after cancel, but how about such code (it won't raise exception):
for(long i = 0; i < 1_000_000_000; i++) {
}
if (isCancelled())
break;
}
System.out.println("Service: END");
return null;
}
};
}
}
#Override
public void start(Stage primaryStage) throws Exception {
MyService myService = new MyService();
myService.start();
Thread.sleep(5000);
myService.cancel();
System.out.println(myService.getState()); // Here is `CANCELLED` already but task isn't finished yet.
// <--- How to wait cancellation of Task here?
System.out.println("This command must be called after `Service: END`");
Platform.exit();
}
public static void main(String[] args) {
launch(args);
}
}
As you known call of Service#cancel doesn't wait cancellation of Task. So, I want to block main thread and await cancellation of Task. How can I do it?
P.S.
Looks like Service doesn't provide any callback/event handler to check real cancellation of Task. Is it right?
By default, Service.cancel() interrupts the Task. So an InterruptedException must be raised and your task will be terminated (forcefully).
One thing you could do is to store the created task in a global variable in your MyService class and override the cancel method like this:
class MyService extends Service {
private Task t;
#Override
public boolean cancel() {
if (t != null) {
return t.cancel(false);
} else {
return false;
}
}
#Override
protected Task createTask() {
t = new Task() { /* ... */ };
return t;
}
}
The rest will be easy. Add a change listener to the service state property (or use setOnCanceled() method) and do whatever you want to do after the state change, in the callback.
Never block the FX Application Thread.
The Service class does indeed define a setOnCancelled(...) method, which you use to register a callback:
myService.setOnCancelled(event -> {
System.out.println("Service was cancelled");
});
Note that when you cancel a Service, it will interrupt the thread if it is blocked. So if you don't catch the InterruptedException it will not exit the call method normally. This is why you don't see the "END" message.
Full example code:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ServiceCancellationTest extends Application {
//NOTICE: This is class in **other file** (here is just for example)
private static class MyService extends Service<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
System.out.println("Service: START");
while(! isCancelled()) {
System.out.println("Service: ITERATION");
try {
Thread.sleep(3000);
} catch (InterruptedException interrupted) {
System.out.println("Task interrupted");
}
if (isCancelled())
break;
}
System.out.println("Service: END");
return null;
}
};
}
}
#Override
public void start(Stage primaryStage) throws Exception {
MyService myService = new MyService();
myService.start();
myService.setOnCancelled(event -> {
System.out.println("In cancelled callback: "+myService.getState()); // Here is `CANCELLED` already but task isn't finished yet.
});
// You should never block the FX Application Thread. To effect a pause,
// use a pause transition and execute the code you want in its
// onFinished handler:
PauseTransition pause = new PauseTransition(Duration.seconds(5));
pause.setOnFinished(event -> {
myService.cancel();
System.out.println("After calling cancel: "+myService.getState());
System.out.println("This command must be called after `Service: END`");
Platform.exit();
});
pause.play();
}
public static void main(String[] args) {
launch(args);
}
}
I'm trying to use this to select a value from a Custom Combo Box:
import java.util.List;
import javafx.application.Application;
import static javafx.application.Application.launch;
import static javafx.application.Application.launch;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class MainApp extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage)
{
final ComboBox<ListGroupsObj> listGroups = new ComboBox();
listGroups.setButtonCell(new GroupListCell());
listGroups.setCellFactory(new Callback<ListView<ListGroupsObj>, ListCell<ListGroupsObj>>()
{
#Override
public ListCell<ListGroupsObj> call(ListView<ListGroupsObj> p)
{
return new GroupListCell();
}
});
listGroups.setEditable(true);
listGroups.setConverter..............
// Insert Some data
ListGroupsObj ob = ListGroupsObj.newInstance().groupId(12).groupName("Test");
listGroups.getItems().addAll(ob);
ListGroupsObj osb = ListGroupsObj.newInstance().groupId(13).groupName("Test2");
listGroups.getItems().addAll(osb);
listGroups.setValue(ob);
// Display the selected Group
listGroups.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<ListGroupsObj>()
{
#Override
public void changed(ObservableValue<? extends ListGroupsObj> arg0, ListGroupsObj arg1, ListGroupsObj arg2)
{
if (arg2 != null)
{
System.out.println("Selected Group: " + arg1.getGroupId() + " - " + arg2.getGroupName());
}
}
});
final StackPane layout = new StackPane();
layout.getChildren().add(listGroups);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 15;");
stage.setScene(new Scene(layout));
stage.show();
}
class GroupListCell extends ListCell<ListGroupsObj>
{
#Override
protected void updateItem(ListGroupsObj item, boolean empty)
{
super.updateItem(item, empty);
if (item != null)
{
setText(item.getGroupId() + " - " + item.getGroupName());
}
}
}
private List<ListGroupsObj> listGroups;
public static class ListGroupsObj
{
private int groupId;
private String groupName;
public static ListGroupsObj newInstance()
{
return new ListGroupsObj();
}
public ListGroupsObj()
{
}
public ListGroupsObj groupId(int groupId)
{
this.groupId = groupId;
return this;
}
public ListGroupsObj groupName(String groupName)
{
this.groupName = groupName;
return this;
}
public int getGroupId()
{
return groupId;
}
public String getGroupName()
{
return groupName;
}
#Override
public String toString()
{
return groupId + " - " + groupName;
}
}
public class GroupConverter extends StringConverter<ListGroupsObj>
{
#Override
public String toString(ListGroupsObj obj)
{
return obj.getGroupId() + " - " + obj.getGroupName();
}
#Override
public ListGroupsObj fromString(String obj)
{
//TODO when you type for example "45 - NextGroup" you want to take only tyhe number"
return ListGroupsObj.newInstance().groupName(obj);
}
}
}
I get this error when I click outside of the comboBox:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.String cannot be cast to com.selectmenuexample.MainApp$ListGroupsObj
I found that this can be done using convertor but I'm now aware how to use it. Can you help with this implementation?
Here is what is wrong:
You called your ComboBox listGroups and your List of Items listGroups. So in your start code, you were hiding that variable. So I removed that useless variable since you can manipulate Items directly in the ComboBox.
You had basically three methods/variable doing the exact same things. Converting your Object into a String with a "-" between them. So I removed the GroupConverter and the custom cellFactory. You don't need them because you already have your "toString()" method in your ListGroupsObj which is doing the job.
Then you misunderstood how the ComboBox is working. If it's editable, the ComboBox will allow something to be typed inside the TextField. That's where the StringConverter comes. It will allow you to make the conversion between a String and your ListGroupsObj and the way around.
In order to go from a ListGroupsObj to a String, simply call the "toString()" method on your object.
But in the way around, you should either create a new ListGroupsObj, or verify that what's inside the ComboBox is not already one item of yours. For example, if you select an Item in the comboBox, the fromString() will be called. But you don't want to create a new ListGroupsObj, you just want to isolate the ListGroupsObj inside your items List and returns it.
Now, you have the guaranty that a call to getValue() on your ComboBox will always return an ListGroupsObj object since you have provided a custom and valid StringConverter.
Here is a simplified and working version of your code :
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class MainApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final ComboBox<ListGroupsObj> comboBox = new ComboBox();
comboBox.setEditable(true);
comboBox.setConverter(new StringConverter<ListGroupsObj>() {
#Override
public String toString(ListGroupsObj obj) {
return obj.toString();
}
#Override
public ListGroupsObj fromString(String obj) {
//Here we try to identify if the given String actually represents one item of our list
for(ListGroupsObj tempObj:comboBox.getItems()){
if(tempObj.toString().equals(obj)){
return tempObj;
}
}
//If not we just create a new one
return ListGroupsObj.newInstance().groupName(obj);
}
});
// Insert Some data
ListGroupsObj ob = ListGroupsObj.newInstance().groupId(12).groupName("Test");
comboBox.getItems().addAll(ob);
ListGroupsObj osb = ListGroupsObj.newInstance().groupId(13).groupName("Test2");
comboBox.getItems().addAll(osb);
comboBox.setValue(ob);
final StackPane layout = new StackPane();
layout.getChildren().add(comboBox);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 15;");
stage.setScene(new Scene(layout));
stage.show();
}
public static class ListGroupsObj {
private int groupId;
private String groupName;
public static ListGroupsObj newInstance() {
return new ListGroupsObj();
}
public ListGroupsObj() {
}
public ListGroupsObj groupId(int groupId) {
this.groupId = groupId;
return this;
}
public ListGroupsObj groupName(String groupName) {
this.groupName = groupName;
return this;
}
public int getGroupId() {
return groupId;
}
public String getGroupName() {
return groupName;
}
#Override
public String toString() {
return groupId + " - " + groupName;
}
}
}
PS: The issue was already raised in the official JavaFX issue Tracker, I'll leave the link here since there is another example in the ticket (login required) : https://javafx-jira.kenai.com/browse/RT-29118
I'm trying to update the GUI status based on a time consuming task. When I push on a button, I want the button to be inactive and the cursor to change until the job is completed. I've come up with this code that mostly works as needed.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class TestWait2 extends Application {
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
primaryStage.setScene(new Scene(root));
MyService myService = new MyService();
primaryStage.getScene().getRoot().cursorProperty()
.bind(Bindings.when(myService.runningProperty())
.then(Cursor.WAIT).otherwise(Cursor.DEFAULT));
Button startButton = new Button();
startButton.setText("Button");
startButton.disableProperty().bind(myService.runningProperty());
root.getChildren().add(startButton);
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
myService.start();
}
});
primaryStage.show();
}
private class MyService extends Service<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(5000);
return null;
}
};
}
}
public static void main(String[] args) {
launch(args);
}
}
When I launch it works great the first time. The problem is that if I click on the button a second time it get an error.
Exception in thread "JavaFX Application Thread" java.lang.IllegalStateException: Can only start a Service in the READY state. Was in state SUCCEEDED
Any thoughts on how to get around that issue?
I'm running on Java 8u5.
The problem with your code that you try to call myService.start(); when its on SUCCEEDED state(since you started it already once).
this is according to javadoc of Service start method
Starts this Service. The Service must be in the READY state to succeed in * *this call.
This method should only be called on the FX application thread.
to make your code work, you need to you call myService.restart().
since you are planning to use your service over and over you can do the following:
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
//replace this line
// myService.start();
//with this
myService.restart();
}
});
Adding this to the program seemed to solve this.
myService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
myService.reset();
}
});
I also found I can add the following directly to the MyService class.
#Override
protected void succeeded() {
reset();
}
There doesn't seem to be any other way to have it go to the ready state after completing it's work.