selectFirst() on javafx ListView dont select the element - javafx

i have small application to manage my tasks. I created DB(sqlite), and i store there a data.
After i log in i got dashboard scene. On dashboard scene i have implemented ObservableList which store taskList and their details. This list is updated always when i add/modify/delete tasks.
I have problem when i add/modify/delete task, when i do it the list is correctly updated but the item witch i add/modify is not selected after operation although i have that implemented.
The Main code:
public class Main extends Application {
private static Main instance;
public BorderPane mainBorderPane;
private Stage primaryStage;
public static Main getInstance() {
return instance;
}
#Override
public void start(Stage primaryStage) throws Exception{
instance = this;
this.primaryStage = primaryStage;
this.primaryStage.setTitle("Hello World");
showDashboardScene();
}
private void showDashboardScene() throws IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/sample/dashboard.fxml"));
mainBorderPane = loader.load();
Scene scene = new Scene(mainBorderPane);
primaryStage.setScene(scene);
primaryStage.initStyle(StageStyle.UTILITY);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
DashboardController:
public class DashboardController {
private ObservableList<Task> tasks = FXCollections.observableArrayList();
#FXML
private ListView<Task> taskListView;
public void initialize() throws SQLException {
tasks = TaskData.getInstance().getTaskList();
findListChange();
taskListView.setItems(tasks);
taskListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
taskListView.getSelectionModel().selectFirst();
}
public void refreshView() {
tasks = TaskData.getInstance().getTaskList();
findListChange();
taskListView.setItems(tasks);
taskListView.getSelectionModel().selectedItemProperty();
taskListView.getSelectionModel().selectLast();
}
private void findListChange() {
this.taskListView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Task>() {
#Override
public void changed(ObservableValue<? extends Task> observable, Task oldValue, Task newValue) {
if (newValue != null) {
Task item = taskListView.getSelectionModel().getSelectedItem();
}
}
});
}
}
dashboard.fxml
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ListView?>
<BorderPane fx:controller="sample.DashboardController" xmlns:fx="http://javafx.com/fxml" >
<top>
<fx:include fx:id="mainMenu" source="/sample/menu.fxml"/>
</top>
<left>
<ListView fx:id="taskListView"/>
</left>
</BorderPane>
MenuController
public class MenuController {
public void showDialog() throws SQLException, IOException {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/sample/addTask.fxml"));
loader.load();
AddTaskController controller = loader.getController();
controller.showAddTaskDialog();
}
}
menu.fxml
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuItem?>
<MenuBar fx:controller="sample.MenuController" xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml ">
<Menu text="Tasks">
<MenuItem text="Add" onAction="#showDialog"/>
</Menu>
</MenuBar>
addTask.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.GridPane?>
<DialogPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="sample.AddTaskController">
<content>
<GridPane vgap="10" hgap="10" >
<Label text="Name" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
<TextField fx:id="taskNameTF" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
</GridPane>
</content>
</DialogPane>
AddTaskController:
public class AddTaskController {
#FXML
private TextField taskNameTF;
#FXML
public void showAddTaskDialog()throws IOException, SQLException {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(Main.getInstance().mainBorderPane.getScene().getWindow());
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/sample/addTask.fxml"));
try {
dialog.getDialogPane().setContent(loader.load());
} catch (IOException e) {e.printStackTrace();
System.out.println("Nie udało sie wyświetlić panelu, prosimy spróbować później");
e.printStackTrace();
return;
}
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Optional<ButtonType> result = dialog.showAndWait();
if(result.isPresent() && result.get().equals(ButtonType.OK)) {
AddTaskController controller = loader.getController();
controller.addTaskToList();
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("/sample/dashboard.fxml"));
fxmlLoader.load();
DashboardController dashboardController = fxmlLoader.getController();
dashboardController.refreshView();
System.out.println("OK, pressed");
} else {
System.out.println("CANCEL, pressed");
}
}
private void addTaskToList() {
String taskName = taskNameTF.getText().trim();
TaskData.getInstance().addTaskToList(new Task(taskName));
}
}
Task
public class Task {
private String taskName;
public Task(String taskName) {
this.taskName = taskName;
}
#Override
public String toString() {
return taskName.toString();
}
}
TaskData
public class TaskData {
private ObservableList<Task> tasks = FXCollections.observableArrayList();
private static TaskData instance = new TaskData();
public static TaskData getInstance() {
return instance;
}
public ObservableList<Task> getTaskList() {
return tasks;
}
public void addTaskToList(Task task) {
instance.tasks.add(task);
}
}
i'm not sure but when i initialize a application, reference to ListView is different than reference of list when i add/modify item. I think this is the problem but i dont know how to fix it ;/

Related

JavaFX getController not updating FXML controller

I have two controllers:
Main which contains TableView Tab which displays SQLITE database tables (which contain different number of columns) in tabular format, and
Rem which contains a button which dictates which database to display (there are many such controllers).
Now if I create a method in Main which is responsible for updating and displaying TableView contents, how do I use that method in other controller?
I created two static ObservableList variables columns and rows and bind them to Tab in Main's initialize.
APP.java
public class App extends Application {
private static Scene scene;
public static Connection conn;
#Override
public void start(Stage stage) throws IOException {
try{
conn=java.sql.DriverManager.getConnection("jdbc:sqlite:sample.db");
if(conn!=null){
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS tab1(Field0 TEXT, Field1 TEXT, Field2 TEXT);");
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS tab2(Field3 TEXT, Field4 TEXT);");
}
}catch(Exception ex){
System.out.println(ex);
}
scene = new Scene(loadFXML("main"), 640, 480);
stage.setScene(scene);
stage.show();
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
public static ResultSet getRS(String table){
String sql="SELECT * FROM "+table;
ResultSet rs=null;
try{
rs=conn.createStatement().executeQuery(sql);
}catch(Exception ex){
System.out.println(ex);
}
return rs;
}
}
MAIN.FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nic.testfx.Main">
<children>
<MenuBar>
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" onAction="#loadRem" text="Load Remaining" />
</items>
</Menu>
</menus>
</MenuBar>
<TableView fx:id="Tab" prefHeight="200.0" prefWidth="200.0" />
<VBox fx:id="remPane" prefHeight="200.0" prefWidth="100.0" />
</children>
</VBox>
MAIN.JAVA
public class Main implements Initializable {
#FXML
public TableView<ObservableList<String>> Tab;
#FXML
private VBox remPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
Tab.itemsProperty().bind(getTabRow());
}
public void makeTable(ResultSet rs){
try{
var rsm=rs.getMetaData();
ObservableList<TableColumn<ObservableList<String>,String>>cols=FXCollections.observableArrayList();
for(int i=0;i<rsm.getColumnCount();i++){
final int j=i;
TableColumn<ObservableList<String>,String> col=new TableColumn(rsm.getColumnName(i+1));
col.setCellValueFactory(param->new SimpleStringProperty(param.getValue().get(j)));
cols.add(col);
}
ObservableList<ObservableList<String>> arows=FXCollections.observableArrayList();
while(rs.next()){
ObservableList<String> row=FXCollections.observableArrayList();
for(int i=1;i<=rsm.getColumnCount();i++){
row.add(rs.getString(i));
}
arows.add(row);
}
setTable(cols,arows);
}catch(Exception ex){
System.out.println(ex);
}
}
private static final ObservableList<TableColumn<ObservableList<String>,String>> columns=FXCollections.observableArrayList();
private static final javafx.beans.property.ListProperty<ObservableList<String>> rows=new javafx.beans.property.SimpleListProperty();
private javafx.beans.property.ListProperty getTabRow(){
Tab.getColumns().clear();
Tab.getColumns().addAll(columns);
return rows;
}
private static void setTable(ObservableList<TableColumn<ObservableList<String>,String>>c,ObservableList<ObservableList<String>> r){
columns.setAll(c);
rows.set(r);
}
#FXML
private void loadRem(ActionEvent event) {
try{
for(int i=remPane.getChildren().size()-1;i>=0;i--){
remPane.getChildren().remove(i);
}
javafx.fxml.FXMLLoader fxmlLoader=new javafx.fxml.FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("rem.fxml"));
javafx.scene.layout.Pane panel=fxmlLoader.load();
remPane.getChildren().add(panel);
makeTable(App.getRS("tab1"));
}catch(Exception ex){
System.out.println(ex);
}
}
}
REM.FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nic.testfx.Rem">
<children>
<Button fx:id="btn" mnemonicParsing="false" onAction="#loadTab" text="Button" />
</children>
</VBox>
REM.JAVA
public class Rem implements Initializable {
#FXML
private Button btn;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void loadTab(ActionEvent event) {
try{
var fxml=new javafx.fxml.FXMLLoader(App.class.getResource("main.fxml"));
fxml.load();
Main controller=fxml.getController();
var rs=App.getRS("tab2");
controller.makeTable(rs);
}catch(Exception ex){
System.out.println(ex);
}
}
}
OK I got it.
Created a java class which encapsulate method makeTable:
MODEL.JAVA
public class Model{
private final TableView table;
public Model(TableView t){
this.table=t;
}
public void makeTable(ResultSet rs){
ObservableList<TableColumn<ObservableList<String>,String>>tabcols=FXCollections.observableArrayList();
ObservableList<ObservableList<String>>tabrows=FXCollections.observableArrayList();
//Create TableColumns and add to tabcols;
while(rs.next()){
//Create ObservableList<String>, get data from rs, and add to tabrows
}
this.table.getColumns().setAll(tabcols);
this.table.getItems().setAll(tabrows);
}
}
Next, in Controller file Main.java created a static instance of Model:
MAIN.JAVA
public class Main implements Initializable{
#FXML
private TableView<ObservableList<String>> Tab;
static Model model;
#Override
public void initialize (URL url, ResourceBundle rb){
model=new Model(Tab);
}
}
Now I can use model from Rem controller:
REM.JAVA
public class Rem implements Initializable{
#FXML
private Button btn;
#Override
public void initialize (URL url, ResourceBundle rb){
//TODO
}
#FXML
private void loadTab(ActionEvent event){
var rs=App.getRS("tab2");
Main.model.makeTable(rs);
}

issue with using extended JavaFX Class Dialog<R> and initializing the DialogPane

I use an extended Dialog class in my JavaFX application, but I failed to initialize the fields of the DialogPane. It seems the initialize method is never invoked. I make several modifications of the program code, unfortunately I do not have success. Therefore I hope you can help me. Thanks!
The application is reduced to a minimum: the main program starts via ControllerFactory the PortalController. There is a button to open my 'DialogFriZi'. The related DialogPane contains a label. The initializing of this label failed.
main program:
public class Main extends Application {
private final Logger logger = Logger.getLogger(this.getClass().getName());
private final String FXML_PORTAL = "portal.fxml";
private MyHandler myHandler = new MyHandler();
#Override
public void init() {
try{
// Thread.currentThread is the FX-Launcher thread:
Thread.currentThread().setUncaughtExceptionHandler(myHandler);
try {
logger.addHandler(new FileHandler("java.myLOG"));
}
catch (IOException e) {
throw new IllegalStateException("IOException when adding File Handler");
}
}
catch (Exception ex) {
myHandler.uncaughtException(Thread.currentThread(), ex);
throw(ex);
}
}
#Override
public void start(Stage primaryStage) {
try{
logger.info("Test Application started");
Thread.currentThread().setUncaughtExceptionHandler(myHandler);
try{
URL location = new URL(this.getClass().getResource("resources/fxml/" + FXML_PORTAL).toString());
FXMLLoader loader = new FXMLLoader(location);
loader.setControllerFactory(new SimpleControllerFactory(primaryStage));
Pane root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("*** TEST App ***");
primaryStage.show();
}
catch(IOException ex) {
ex.printStackTrace();
throw new IllegalStateException("check program source code!");
}
}
catch(Exception ex) {
myHandler.uncaughtException(Thread.currentThread(), ex);
throw(ex);
}
}
public static void main(String[] args) {
launch(args);
}
class MyHandler implements Thread.UncaughtExceptionHandler{
#Override
public void uncaughtException(Thread thread, Throwable throwable) {
logger.log(Level.SEVERE, "** STOPP ** due to uncaught exception", throwable);
Platform.exit();
}
}
}
ControllerFactory
public class SimpleControllerFactory implements Callback<Class<?>,Object> {
private static final Logger logger = Logger.getLogger(SimpleControllerFactory.class.getName());
private final Stage primaryStage;
// types of controller
public SimpleControllerFactory(Stage stage) {
this.primaryStage = stage;
}
public SimpleControllerFactory() { this(null); }
#Override
public Object call(Class<?> type) {
try {
for (var c : type.getConstructors()) {
switch(c.getParameterCount()) {
case 0 -> { }
case 1 -> {
if ( c.getParameterTypes()[0] == Stage.class) {
return c.newInstance(primaryStage) ;
}
else;
}
default -> { }
}
}
return type.getDeclaredConstructor().newInstance();
}
catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException ex) {
logger.log(Level.SEVERE,ex.toString(),ex);
throw new RuntimeException(ex);
}
}
}
PortalController
public class PortalController implements Initializable {
private static final Logger logger = Logger.getLogger(PortalController.class.getName());
private final Stage recentStage;
private ResourceBundle resourceBundle;
public PortalController(Stage stage) {
this.recentStage = stage;
}
public PortalController() {
this(null);
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
#FXML
private void start(ActionEvent event) {
DialogFriZi dialog = null;
dialog = new DialogFriZi(recentStage);
Optional<Boolean> result = dialog.showAndWait();
}
}
related FXML file
<VBox prefHeight="500.0" prefWidth="400.0" spacing="5.0" style="-fx-padding: 5 5 5 5;-fx-font-size: 11px" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="TestSimpleDialog.PortalController">
<children>
<HBox alignment="CENTER">
<children>
<Button mnemonicParsing="false" onAction="#start" text="start FriZi dialog">
<HBox.margin>
<Insets top="50.0" />
</HBox.margin>
</Button>
</children>
</HBox>
</children>
the extended Dialog class
public class DialogFriZi extends Dialog<Boolean> {
#FXML private ButtonType okButtonType;
#FXML Label label;
private static final Logger logger = Logger.getLogger(DialogFriZi.class.getName());
private final Stage recentStage;
public DialogFriZi(Stage primaryStage) {
this.recentStage = primaryStage;
runDialog();
}
public DialogFriZi() {
this(null);
}
#FXML private void initialize() {
System.out.println("start initializing");
label.setText("hello");
}
private void runDialog() {
FXMLLoader loader = new FXMLLoader();
try {
loader.setLocation(new URL
(this.getClass().getResource("resources/fxml/dialogFriZi.fxml").toString()));
DialogPane dialogPane = loader.load();
loader.setController(this);
initOwner(recentStage);
initModality(Modality.APPLICATION_MODAL);
setResizable(true);
setTitle("FriZi Dialog");
setDialogPane(dialogPane);
}
catch (IOException e) {
String message = "illegale state due to problem with 'resource dialogFriZi.fxml'";
logger.log(Level.SEVERE,message);
throw new IllegalStateException(message);
}
}
}
related FXML file
<DialogPane prefHeight="400.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<content>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<Label fx:id="label" text="Label" />
</children></HBox>
</content>
<buttonTypes>
<ButtonType buttonData="OK_DONE" text="ok" />
<ButtonType buttonData="CANCEL_CLOSE" text="Abbrechen" />
</buttonTypes>
The initalize() method is invoked on the controller, if there is one, during the execution of the load() method. You are setting the controller after calling load(), so when load() runs, there is no controller. You need to change the order of the calls:
private void runDialog() {
FXMLLoader loader = new FXMLLoader();
try {
loader.setLocation(new URL
(this.getClass().getResource("resources/fxml/dialogFriZi.fxml").toString()));
loader.setController(this);
DialogPane dialogPane = loader.load();
// loader.setController(this);
initOwner(recentStage);
initModality(Modality.APPLICATION_MODAL);
setResizable(true);
setTitle("FriZi Dialog");
setDialogPane(dialogPane);
}
catch (IOException e) {
String message = "illegale state due to problem with 'resource dialogFriZi.fxml'";
logger.log(Level.SEVERE,message);
throw new IllegalStateException(message);
}
}

How to bind nested Task progress property to TableView in JavaFX?

Enironment:
OpenJDK12, JavaFX 11
Context: I'm trying to show the Task progress to a TableView, for that, when my code was less complex, my Task object included the bean properties, and the TableView datamodel was my Task object.
public class MyTask extends Task<Void>{
private String name;
//other properties
public Void call() {
//"progress" property is inherited from Task.
//do something and updateProgress()
}
}
public class MyController {
...
#FXML
private TableView<MyTask> dataTable;
#FXML
private TableColumn<MyTask,Double> progressCol;
...
progressCol.setCellValueFactory(new PropertyValueFactory<MyTask, Double>("progress"));
progressCol.setCellFactory(ProgressCell.<Double>forTableColumn());
...
}
That worked fine. But I wanted to separate the Task from the bean properties, so I decided to make a kind of wrapper, but I'm unable to retrieve the progress property anymore.
EDIT
Sample Code:
MyApp
public class MyApp extends Application {
#Override
public void start(Stage stage) throws IOException {
stage.setMinWidth(800);
stage.setMinHeight(500);
FXMLLoader sceneLoader = new FXMLLoader(MyApp.class.getResource("MyScene.fxml"));
Parent parent = sceneLoader.load();
Scene scene = new Scene(parent);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
MyController
public class MyController implements Initializable{
#FXML
private TableView<MyWrapper> dataTable;
#FXML
private TableColumn<MyWrapper, String> nameColumn;
#FXML
private TableColumn<MyWrapper, Double> progressColumn;
public MyController() {
}
#Override
public void initialize(URL location, ResourceBundle resources) {
nameColumn.setCellValueFactory((TableColumn.CellDataFeatures<MyWrapper, String> download) -> download.getValue()
.getMyBean().nameProperty());
//This line only works when MyWrapper has progressPropery() method
//progressColumn.setCellValueFactory(new PropertyValueFactory<>("progress"));
progressColumn.setCellFactory(ProgressCell.<Double>forTableColumn());
MyWrapper w1 = new MyWrapper("qqqqqqq");
MyWrapper w2 = new MyWrapper("wwwwww");
MyWrapper w3 = new MyWrapper("eeeeeee");
ObservableList<MyWrapper> obsList = FXCollections.observableArrayList();
obsList.addAll(w1,w2,w3);
dataTable.setItems(obsList);
Thread t1 = new Thread(w1.getMyTask());
t1.start();
}
MyWrapper
public class MyWrapper {
private SimpleObjectProperty<MyBean> myBean;
private SimpleObjectProperty<MyTask> myTask;
public MyWrapper(String name) {
myBean = new SimpleObjectProperty<MyBean>();
myBean.setValue(new MyBean());
myBean.getValue().setName(name);
myTask = new SimpleObjectProperty<MyTask>();
myTask.setValue(new MyTask());
}
public MyBean getMyBean() {
return myBean.getValue();
}
public MyTask getMyTask() {
return myTask.getValue();
}
}
MyBean
public class MyBean {
private SimpleStringProperty name;
public MyBean() {
name = new SimpleStringProperty("--");
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.setValue(name);
}
}
MyTask
public class MyTask extends Task<Void>{
#Override
protected Void call() throws Exception {
// Set the total number of steps in our process
double steps = 1000;
// Simulate a long running task
for (int i = 0; i < steps; i++) {
Thread.sleep(10); // Pause briefly
// Update our progress and message properties
updateProgress(i, steps);
updateMessage(String.valueOf(i));
} return null;
}
}
ProgressCell
public class ProgressCell extends TableCell<MyWrapper, Double> {
private ProgressBar bar;
private ObservableValue<Double> observable;
private StringProperty colorProperty = new SimpleStringProperty();
public ProgressCell() {
bar = new ProgressBar();
bar.setMaxWidth(Double.MAX_VALUE);
bar.setProgress(0f);
bar.styleProperty().bind(colorProperty);
}
public static <S> Callback<TableColumn<MyWrapper, Double>, TableCell<MyWrapper, Double>> forTableColumn() {
return param -> new ProgressCell();
}
#Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
final TableColumn<MyWrapper, Double> column = getTableColumn();
observable = column == null ? null : column.getCellObservableValue(getIndex());
if (observable != null) {
bar.progressProperty().bind(observable);
} else if (item != null) {
bar.setProgress(item);
}
setGraphic(bar);
}
}
}
MyScene.fxml
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.effect.Blend?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.StackPane?>
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.java.MyController">
<StackPane BorderPane.alignment="CENTER">
<children>
<TableView id="dataTable" fx:id="dataTable" prefHeight="193.0" prefWidth="678.0" snapToPixel="false">
<columns>
<TableColumn fx:id="nameColumn" editable="false" prefWidth="88.0" text="Name" />
<TableColumn fx:id="progressColumn" editable="false" prefWidth="75.0" text="Progress" />
</columns>
<effect>
<Blend />
</effect>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</StackPane>
</AnchorPane>
I don't know how to get the progress bar working, without adding the progressProperty() method in MyWrapper. I was expecting to access the progress property like the name property. Is there some way ? How do you think it would be better?
Any help appreciated.
There is no support for nested properties (as you noticed and I confirmed in a comment that mysteriously disappeared .. ) - providing the property in a custom cellValueFactory that walks down the tree is the way to go: just do the same for the progress of the task as you do for the name of the bean.
A working code snippet:
// column setup
nameColumn.setCellValueFactory(cc -> cc.getValue().getMyBean().nameProperty());
progressColumn.setCellValueFactory(cc -> cc.getValue().getMyTask().progressProperty().asObject());
progressColumn.setCellFactory(ProgressBarTableCell.forTableColumn());
new Thread(w1.getMyTask()).start();
Note the conversion of DoubleProperty to ObjectProperty<Double> (as Slaw noted in a comment that disappeared as well ;)
Whether or not such deep diving is a good idea depends on your context: it's okay as long as the data is read-only and doesn't change over its lifetime. Otherwise, you would need to take precautions to guard against such change. Which will require additonal logic in the wrapper anyway, so exposing the properties of interest in that layer probably would be the cleaner approach.
The first error is thrown because your MyObject class doesn't have a progressProperty function.
If you add this function to your wrapper class it will work.
public ReadOnlyDoubleProperty progressProperty() {
return task.progressProperty();
}
.
progressCol.setCellValueFactory(new PropertyValueFactory<>("progress"));

JavaFX - Realtime lineChart update from thread

In my JavaFX application, I would like to display live data from a background thread. Does anyone know how to update a linechart from a background thread? Thank you. Below some sample code.
TM
Preview image
Sample controller
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.control.Button;
public class SampleController {
#FXML
Button btnStart;
#FXML
Button btnStop;
#FXML
LineChart myChart;
Process process;
#FXML
public void initialize() {
process = new Process();
}
public void start(ActionEvent event) {
process.start();
}
public void stop(ActionEvent event) {
process.stop();
}
}
Process class. Launches the thread.
public class Process {
private Task task = new Task();
public void start(){
task.start();
}
public void stop(){
task.kill();
}
}
Task class. The thread class which executes the tasks
public class Task extends Thread {
private boolean isActive = true;
#Override
public void run() {
while (isActive) {
try {
// Simulate heavy processing stuff
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Add a new number to the linechart
// Remove first number of linechart
}
}
public void kill(){
isActive = false;
}
}
Main class
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, 500, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/10.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sample.SampleController">
<children>
<Button fx:id="btnStart" mnemonicParsing="false" onAction="#start" text="Start" />
<LineChart fx:id="myChart">
<xAxis>
<CategoryAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</LineChart>
<Button fx:id="btnStop" mnemonicParsing="false" onAction="#stop"
text="Stop" />
</children>
</VBox>
You need to insert the values to display in the LineChart from the UI Thread. Therefore you can simply use something like this:
public class Task extends Thread {
private boolean isActive = true;
private LineChart<String, Number> chart;
private Series<String, Number> series = new Series<>();
public Task(LineChart<CategoryAxis, NumberAxis> chart) {
this.chart.getData().add(series);
}
#Override
public void run() {
while (isActive) {
try {
// Simulate heavy processing stuff
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(() -> {
// Add a new number to the linechart
this.series.getData().add(new Data<>("",0));
// Remove first number of linechart
this.series.getData().remove(0);
});
}
}
public void kill(){
isActive = false;
}
}
or if you want to execute something after you added and removed the values use something like:
public class Task extends Thread {
private boolean isActive = true;
private LineChart<String, Number> chart;
private Series<String, Number> series = new Series<>();
public Task(LineChart<CategoryAxis, NumberAxis> chart) {
this.chart.getData().add(series);
}
#Override
public void run() {
while (isActive) {
try {
// Simulate heavy processing stuff
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
FutureTask<Object> task = new FutureTask<Object>(new Callable<Object>() {
#Override
public Object call() throws Exception {
// Add a new number to the linechart
series.getData().add(new Data<>("",0));
// Remove first number of linechart
series.getData().remove(0);
return true;
}
});
Platform.runLater(task);
try {
System.out.println(task.get());
//TODO after insertion
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
public void kill(){
isActive = false;
}
}

ChoiceBox: freeze when removing selected item from another ChoiceBox

I want to use two ChoiceBoxes offering the same select items with the exception of the one selected by the other. However, after selecting options a few times, the entire java process becomes unresponsive.
Update: the issue does not occur if I use ComboBox instead of ChoiceBox. However, an explanation for why it happens would be interesting.
I have two ChoiceBoxes
#FXML private ChoiceBox<String> firstCB;
#FXML private ChoiceBox<String> secondCB;
that initially have the same selection options
firstCB.getItems().addAll(Charset.availableCharsets().keySet());
secondCB.getItems().addAll(Charset.availableCharsets().keySet());
and event listeners to remove the new selection from the other ChoiceBox's options and make the old option available again
firstCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
secondCB.getItems().remove(newVal);
secondCB.getItems().add(oldVal);
});
the equivalent Swing code with JComboBoxes and the following event handler works
firstCB.addItemListener(itemEvent -> {
if (itemEvent.getStateChange() == ItemEvent.DESELECTED) {
secondCB.addItem((String) itemEvent.getItem());
} else if (itemEvent.getStateChange() == ItemEvent.SELECTED) {
secondCB.removeItem(itemEvent.getItem());
}
});
Full code:
class test.Main
public class Main extends Application {
private Parent rootPane;
#Override
public void start(Stage arg0) throws Exception {
Scene scene = new Scene(rootPane);
arg0.setScene(scene);
arg0.show();
}
#Override
public void init() throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"));
rootPane = loader.load();
loader.<View>getController().init();
}
public static void main(String[] args) {
launch(args);
}
}
class test.View
public class View {
#FXML
private ChoiceBox<String> firstCB;
#FXML
private ChoiceBox<String> secondCB;
public void init() {
firstCB.getItems().addAll(Charset.availableCharsets().keySet());
secondCB.getItems().addAll(Charset.availableCharsets().keySet());
firstCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
System.out.printf("[%s]firstCB selection changed%n", Thread.currentThread().getName());
secondCB.getItems().remove(newVal);
secondCB.getItems().add(oldVal);
});
// removing one of the event listeners doesn't help
secondCB.getSelectionModel().selectedItemProperty()
.addListener((observable, oldVal, newVal) -> {
System.out.printf("[%s]secondCB selection changed%n", Thread.currentThread().getName());
firstCB.getItems().remove(newVal);
firstCB.getItems().add(oldVal);
});
}
}
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<Pane fx:id="rootPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="90.0" prefWidth="180.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.View">
<children>
<ChoiceBox fx:id="firstCB" layoutX="14.0" layoutY="14.0" prefWidth="150.0" />
<ChoiceBox fx:id="secondCB" layoutX="14.0" layoutY="52.0" prefWidth="150.0" />
</children>
</Pane>
try this, it works fine for me
firstComboBox.valueProperty().addListener((obs, oldV, newV) -> {
secondComboBox.getItems().remove(newV);
if(oldV!= null) secondComboBox.getItems().add(oldV);
});
secondComboBox.valueProperty().addListener((obs, oldV, newV) -> {
firstComboBox.getItems().remove(newV);
if(oldV!= null) firstComboBox.getItems().add(oldV);
});
or other not best solution with observable and filtered list
public class Controller implements Initializable{
public ComboBox<String> firstComboBox;
public ComboBox<String> secondComboBox;
#Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<String> list = FXCollections.observableArrayList();
list.addAll("one", "two", "three", "four", "five");
firstComboBox.getItems().addAll(list);
secondComboBox.getItems().addAll(list);
firstComboBox.valueProperty().addListener(c -> refreshList(list, secondComboBox, firstComboBox));
secondComboBox.valueProperty().addListener(c -> refreshList(list, firstComboBox, secondComboBox));
}
private void refreshList(ObservableList<String> list, ComboBox<String> choiceBox, ComboBox<String> choiceBox2) {
String tmp = choiceBox.getValue();
FilteredList<String> filteredList = new FilteredList<>(list, string -> !string.equals(choiceBox2.getValue()));
choiceBox.setItems(filteredList);
choiceBox.setValue(tmp);
}

Resources