What is the best strategy for populating a TableView from a service?
- by alrutherford
I have an application which has a potentially long running background process. I want this process to populate a TableView as results objects are generated. The results objects are added to an observableList and have properties which are bound to the columns in the usual fashion for JavaFX.
As an example of this consider the following sample code
Main Application
import java.util.LinkedList;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DataViewTest extends Application
{
private TableView<ServiceResult> dataTable = new TableView<ServiceResult>();
private ObservableList<ServiceResult> observableList;
private ResultService resultService;
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage stage)
{
observableList = FXCollections.observableArrayList(new LinkedList<ServiceResult>());
resultService = new ResultService(observableList);
Button refreshBtn = new Button("Update");
refreshBtn.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent arg0)
{
observableList.clear();
resultService.reset();
resultService.start();
}
});
TableColumn<ServiceResult, String> nameCol = new TableColumn<ServiceResult, String>("Value");
nameCol.setCellValueFactory(new PropertyValueFactory<ServiceResult, String>("value"));
nameCol.setPrefWidth(200);
dataTable.getColumns().setAll(nameCol);
// productTable.getItems().addAll(products);
dataTable.setItems(observableList);
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(300);
stage.setHeight(500);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(refreshBtn, dataTable);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
}
Service
public class ResultService extends Service<Void>
{
public static final int ITEM_COUNT = 100;
private ObservableList<ServiceResult> observableList;
/**
* Construct service.
*
*/
public ResultService(ObservableList<ServiceResult> observableList)
{
this.observableList = observableList;
}
@Override
protected Task<Void> createTask()
{
return new Task<Void>()
{
@Override
protected Void call() throws Exception
{
process();
return null;
}
};
}
public void process()
{
for (int i = 0; i < ITEM_COUNT; i++)
{
observableList.add(new ServiceResult(i));
}
}
}
Data
public class ServiceResult
{
private IntegerProperty valueProperty;
/**
* Construct property object.
*
*/
public ServiceResult(int value)
{
valueProperty = new SimpleIntegerProperty();
setValue(value);
}
public int getValue()
{
return valueProperty.get();
}
public void setValue(int value)
{
this.valueProperty.set(value);
}
public IntegerProperty valueProperty()
{
return valueProperty;
}
}
Both the service and the TableView share a reference to the observable list? Is this good practise in JavaFx and if not what is the correct strategy?
If you hit the the 'Update' button the list will not always refresh to the ITEM_COUNT length. I believe this is because the observableList.clear() is interfering with the update which is running in the background thread. Can anyone shed some light on this?