I want to trigger a method or action when the user check or uncheck a checkbox in the tableView. the coursData.addListener(...) doesn't get triggered when the user use the checkBox .
Here is my code it compiles and the windows appears with the tableView with checkbox.
package testCheckBox2;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class CheckBoxTableCellTest extends Application {
private TableView<cours> tableView;
public ObservableList<cours> coursData ;//= FXCollections.observableArrayList();
#Override
public void start(Stage primaryStage) {
this.tableView = new TableView<cours>();
final TableColumn<cours, String> Cours = new TableColumn<cours, String>("Cours");
final TableColumn<cours, Boolean> checkedCol = new TableColumn<cours, Boolean>("Checked");
this.coursData =FXCollections.observableArrayList(
new cours("Analyse", "3"),
new cours("Analyse TP", "4"),
new cours("Thermo", "5"),
new cours("Thermo TP", "7"),
new cours("Chimie", "8"));
tableView.setItems(this.coursData);
tableView.getColumns().addAll(Cours, checkedCol);
Cours.setCellValueFactory(new PropertyValueFactory<cours, String>("cours"));
checkedCol.setCellValueFactory(new PropertyValueFactory<cours, Boolean>("checked"));
checkedCol.setCellFactory(CheckBoxTableCell.forTableColumn(checkedCol));
checkedCol.setEditable(true);
tableView.setEditable(true);
final BorderPane root = new BorderPane();
root.setCenter(tableView);
coursData.addListener(new InvalidationListener() {
#Override public void invalidated(Observable o) {
System.out.println("checkBox change state ");
//Here is my problem.
//When the user click on a checkBox , the method isn't call .
}
});
Scene scene = new Scene(root, 300, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class cours {
private StringProperty cours;
private StringProperty coursID;
private BooleanProperty checked;
public cours(String cours, String coursID) {
this.cours = new SimpleStringProperty(cours);
this.coursID = new SimpleStringProperty(coursID);
this.checked = new SimpleBooleanProperty(false);
}
public String getCours() {
return cours.get();
}
public String getCoursID() {
return coursID.get();
}
public boolean isChecked() {
return checked.get();
}
public void setCours(String cours) {
this.cours.set(cours);
}
public void setCoursID(String coursID) {
this.coursID.set(coursID);
}
public void setChecked(boolean checked) {
this.checked.set(checked);
}
public StringProperty coursProperty() {
return cours;
}
public StringProperty coursIDProperty() {
return coursID;
}
public BooleanProperty checkedProperty() {
return checked;
}
}
}
You have two ways of getting a notification when any of the check boxes is clicked.
One: providing a callback as argument for CheckBoxTableCell.forTableColumn instead of the tableColumn:
checkedCol.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer param) {
System.out.println("Cours "+items.get(param).getCours()+" changed value to " +items.get(param).isChecked());
return items.get(param).checkedProperty();
}
}));
Two: providing a callback to the collection:
final List<Cours> items=Arrays.asList(new Cours("Analyse", "3"),
new Cours("Analyse TP", "4"),
new Cours("Thermo", "5"),
new Cours("Thermo TP", "7"),
new Cours("Chimie", "8"));
this.coursData = FXCollections.observableArrayList(new Callback<Cours, Observable[]>() {
#Override
public Observable[] call(Cours param) {
return new Observable[] {param.checkedProperty()};
}
});
coursData.addAll(items);
and now listening to changes in the collection:
coursData.addListener(new ListChangeListener<Cours>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Cours> c) {
while (c.next()) {
if (c.wasUpdated()) {
System.out.println("Cours "+items.get(c.getFrom()).getCours()+" changed value to " +items.get(c.getFrom()).isChecked());
}
}
}
});
Related
I'm trying to do the following in JavaFX:
Have a TableView with multiple rows.
Each row contains columns with text and one Progress/Status column.
When a specific Button is pressed, for each row of the TableView some task should be performed, one row after the other. (e.g. check some data, ...)
While this task is performed, a indeterminate ProgressIndicator shall be shown in the Status column, until the task for this row is finished, then the indicator shows as done.
When all tasks for each row are done, the button can be pressed again to reset the status and execute the tasks again.
I had found some help in this related Stackoverflow post and also here and tried to tweak this as needed but got stuck on some issues:
Currently, each ProgressIndicator for each row is displayed immediately (as indeterminate) when I run the program. How can I only activate them / make them visible for each row one after another once the button is pressed?
Pressing the button again once the fake tasks are done does not restart it. How would I have to modify / rebuild the program to make resets possible?
Does the overall approach make sense?
My current runnable code:
import java.util.Random;
import java.util.concurrent.*;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ProgressIndicatorTableCellTest extends Application {
public void start(Stage primaryStage) {
TableView<TestTask> table = new TableView<>();
Random rng = new Random();
for (int i = 0; i < 3; i++) {
table.getItems().add(new TestTask(rng.nextInt(3000) + 2000, "Test"));
}
TableColumn<TestTask, String> nameCol = new TableColumn("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>("name"));
nameCol.setPrefWidth(75);
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>("progress"));
progressCol.setCellFactory(ProgressIndicatorTableCell.<TestTask>forTableColumn());
table.getColumns().addAll(nameCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
Button btn = new Button("Start");
btn.setOnAction(actionEvent -> {
ExecutorService executor = Executors.newSingleThreadExecutor();
for (TestTask task : table.getItems()) {
executor.submit(task);
}
});
root.setBottom(btn);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class TestTask extends Task<Void> {
private final int waitTime; // milliseconds
final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
public static final int NUM_ITERATIONS = 100;
public TestTask(int waitTime, String name) {
this.waitTime = waitTime;
this.name.set(name);
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
#Override
protected Void call() throws Exception {
this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
Thread.sleep(waitTime);
this.updateProgress(1, 1);
return null;
}
}
}
class ProgressIndicatorTableCell<S> extends TableCell<S, Double> {
public static <S> Callback<TableColumn<S, Double>, TableCell<S, Double>> forTableColumn() {
return new Callback<TableColumn<S, Double>, TableCell<S, Double>>() {
#Override
public TableCell<S, Double> call(TableColumn<S, Double> param) {
return new ProgressIndicatorTableCell<>();
}
};
}
private final ProgressIndicator progressIndicator;
private ObservableValue observable;
public ProgressIndicatorTableCell() {
this.progressIndicator = new ProgressIndicator();
setGraphic(progressIndicator);
}
#Override
public void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
progressIndicator.progressProperty().unbind();
observable = getTableColumn().getCellObservableValue(getIndex());
if (observable != null) {
progressIndicator.progressProperty().bind(observable);
} else {
progressIndicator.setProgress(item);
}
setGraphic(progressIndicator);
}
}
}
And the current output:
Here is a version that implements your first question. With this requirement, the cell is only a function of the task's state. If it's RUNNING, display an indeterminate progress indicator; if it's SUCCEEDED display a progress indicator with value 1; otherwise, display nothing.
Note the original question is very old and uses a lot of outdated code styles. I've updated accordingly.
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ProgressIndicatorTableCellTest extends Application {
public void start(Stage primaryStage) {
TableView<TestTask> table = new TableView<>();
Random rng = new Random();
for (int i = 0; i < 3; i++) {
table.getItems().add(new TestTask(rng.nextInt(3000) + 2000, "Test"));
}
TableColumn<TestTask, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(data -> data.getValue().nameProperty());
nameCol.setPrefWidth(75);
TableColumn<TestTask, Worker.State> progressCol = new TableColumn<>("Progress");
progressCol.setCellValueFactory(data -> data.getValue().stateProperty());
progressCol.setCellFactory(col -> new ProgressIndicatorTableCell<>());
table.getColumns().addAll(nameCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
Button btn = new Button("Start");
btn.setOnAction(actionEvent -> {
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
});
for (TestTask task : table.getItems()) {
executor.submit(task);
}
});
root.setBottom(btn);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class TestTask extends Task<Void> {
private final int waitTime; // milliseconds
final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
public static final int NUM_ITERATIONS = 100;
public TestTask(int waitTime, String name) {
this.waitTime = waitTime;
this.name.set(name);
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
#Override
protected Void call() throws Exception {
this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
Thread.sleep(waitTime);
this.updateProgress(1, 1);
return null;
}
}
}
class ProgressIndicatorTableCell<S> extends TableCell<S, Worker.State> {
private final ProgressIndicator progressIndicator = new ProgressIndicator();
#Override
protected void updateItem(Worker.State state, boolean empty) {
super.updateItem(state, empty);
if (state == Worker.State.SUCCEEDED) {
progressIndicator.setProgress(1);
setGraphic(progressIndicator);
} else if (state == Worker.State.RUNNING) {
progressIndicator.setProgress(-1);
setGraphic(progressIndicator);
} else {
setGraphic(null);
}
}
}
To allow for "restarting", you should use a Service instead of just a Task. This version will allow for a restart if the button is pressed multiple times, returning everything to the initial state before proceeding.
This version also factors the processing work out of the model class, which is desirable for properly assigning responsibilities to classes:
Item.java:
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleObjectProperty;
public class Item {
public enum State {WAITING, PROCESSING, READY}
final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.WAITING);
public Item(String name) {
this.name.set(name);
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
public State getState() {
return state.get();
}
public ObjectProperty<State> stateProperty() {
return state;
}
public void setState(State state) {
this.state.set(state);
}
}
ProcessManager.java:
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import java.util.List;
import java.util.Random;
public class ProcessManager {
private final List<Item> items;
private Random rng = new Random();
private Service<Void> service = new Service<>() {
#Override
protected Task<Void> createTask() {
return new Task<>() {
#Override
protected Void call() throws Exception {
for (Item task: items) {
try {
Platform.runLater(() -> task.setState(Item.State.PROCESSING));
Thread.sleep(2000 + rng.nextInt(3000));
Platform.runLater(() -> task.setState(Item.State.READY));
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
}
if (isCancelled()) {
Platform.runLater(() -> task.setState(Item.State.WAITING));
break;
}
}
return null;
}
};
}
};
public ProcessManager(List<Item> items) {
this.items = items ;
service.setOnCancelled(e -> items.forEach(task -> task.setState(Item.State.WAITING)));
}
public void process() {
service.restart();
}
}
and the application:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ProgressIndicatorTableCellTest extends Application {
public void start(Stage primaryStage) {
ObservableList<Item> tasks = FXCollections.observableArrayList();
ProcessManager processManager = new ProcessManager(tasks);
TableView<Item> table = new TableView<>();
for (int i = 0; i < 3; i++) {
Item task = new Item("Item " + (i + 1));
tasks.add(task);
}
table.setItems(tasks);
TableColumn<Item, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(data -> data.getValue().nameProperty());
nameCol.setPrefWidth(75);
TableColumn<Item, Item.State> progressCol = new TableColumn<>("Progress");
progressCol.setCellValueFactory(data -> data.getValue().stateProperty());
progressCol.setCellFactory(col -> new TableCell<>() {
private final ProgressIndicator indicator = new ProgressIndicator();
#Override
protected void updateItem(Item.State state, boolean empty) {
super.updateItem(state, empty);
if (state == Item.State.PROCESSING) {
indicator.setProgress(-1);
setGraphic(indicator);
} else if (state == Item.State.READY) {
indicator.setProgress(1);
setGraphic(indicator);
} else {
setGraphic(null);
}
}
});
table.getColumns().addAll(nameCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
Button btn = new Button("Start");
btn.setOnAction(actionEvent -> processManager.process());
root.setBottom(btn);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I am making a to do list with java FXML using file reader and writer and scene builder. I have been trying to add a completed task function but tried many ways and am unable to figure out something which will work for me. I tried making a separate list where I keep my completed task but its still not working out for me. At this moment I have tried a lot by myself and am willing to go for another for another method to add this function. I am using To Do Date Class which is using to do Item class.
I have tried using it as predicate of filtered list and type casting accordingly but it hasn't worked out for me either.Rest of code works perfectly fine.
controller is:
package todolist;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Predicate;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.util.Callback;
/**
*
* #author Pc Planet
*/
public class FXMLDocumentController {
private List<ToDoItem> todoItems;
#FXML
private ListView<ToDoItem> todoListView;
#FXML
private TextArea itemDetailsTextArea;
#FXML
private Label deadlineLabel;
#FXML
private BorderPane mainBorderPane;
#FXML
private ContextMenu listContextMenu;
#FXML
private ToggleButton filterToggleButton;
#FXML
private Button exit,ADD;
private FilteredList<ToDoItem> filteredList;
private Predicate<ToDoItem> wantAllItems;
private Predicate<ToDoItem> wantTodaysItems;
private Predicate<ToDoItem> uncompleted;
private ObservableList<ToDoItem> completed;
public void initialize() {
// delete
listContextMenu = new ContextMenu();
MenuItem deleteMenuItem = new MenuItem("Delete");
deleteMenuItem.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
ToDoItem item = todoListView.getSelectionModel().getSelectedItem();
deleteItem(item);
}
});
// Completed
listContextMenu = new ContextMenu();
MenuItem completedMenuItem = new MenuItem("Mark as complete");
completedMenuItem.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
ToDoItem item = todoListView.getSelectionModel().getSelectedItem();
completedItem(item);
}
});
listContextMenu.getItems().addAll(deleteMenuItem);
listContextMenu.getItems().addAll(completedMenuItem);
//change
todoListView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<ToDoItem>() {
#Override
public void changed(ObservableValue<? extends ToDoItem> observable, ToDoItem oldValue, ToDoItem newValue) {
if(newValue != null) {
ToDoItem item = todoListView.getSelectionModel().getSelectedItem();
itemDetailsTextArea.setText(item.getDetails());
DateTimeFormatter df = DateTimeFormatter.ofPattern("MMMM d, yyyy"); // "d M yy");
deadlineLabel.setText(df.format(item.getDeadline()));
}
}
});
wantAllItems = (ToDoItem todoItem) -> true;
wantTodaysItems = (ToDoItem todoItem) -> (todoItem.getDeadline().equals(LocalDate.now()));
filteredList = new FilteredList<ToDoItem>(ToDoData.getInstance().getTodoItems(), wantAllItems);
// list sort
SortedList<ToDoItem> sortedList = new SortedList<ToDoItem>(filteredList,
new Comparator<ToDoItem>() {
#Override
public int compare(ToDoItem o1, ToDoItem o2) {
return o1.getDeadline().compareTo(o2.getDeadline());
}
});
todoListView.setItems(sortedList);
todoListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
todoListView.getSelectionModel().selectFirst();
todoListView.setCellFactory(new Callback<ListView<ToDoItem>, ListCell<ToDoItem>>() {
#Override
public ListCell<ToDoItem> call(ListView<ToDoItem> param) {
ListCell<ToDoItem> cell = new ListCell<ToDoItem>() {
#Override
protected void updateItem(ToDoItem item, boolean empty) {
super.updateItem(item, empty);
if(empty) {
setText(null);
} else {
setText(item.getShortDescription());
if(item.getDeadline().isBefore(LocalDate.now().plusDays(1))) {
setTextFill(Color.RED);
} else {
setTextFill(Color.GREEN);
}
}
}
};
cell.emptyProperty().addListener(
(obs, wasEmpty, isNowEmpty) -> {
if(isNowEmpty) {
cell.setContextMenu(null);
} else {
cell.setContextMenu(listContextMenu);
}
});
return cell;
}
});
}
#FXML
public void showNewItemDialog() {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(mainBorderPane.getScene().getWindow());
dialog.setTitle("Add New Todo Item");
dialog.setHeaderText("Use this dialog to create a new todo item");
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("todoitemdialog.fxml"));
try {
dialog.getDialogPane().setContent(fxmlLoader.load());
} catch(IOException e) {
System.out.println("Couldn't load the dialog");
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() == ButtonType.OK) {
DialogController controller = fxmlLoader.getController();
ToDoItem newItem = controller.processResults();
todoListView.getSelectionModel().select(newItem);
}
}
#FXML
public void completedtasks(ActionEvent event){
}
#FXML
public void handleKeyPressed(KeyEvent keyEvent) {
ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem();
if(selectedItem != null) {
if(keyEvent.getCode().equals(KeyCode.DELETE)) {
deleteItem(selectedItem);}
else{
completedItem(selectedItem);
}
}
}
public void handleClickListView() {
ToDoItem item = todoListView.getSelectionModel().getSelectedItem();
itemDetailsTextArea.setText(item.getDetails());
deadlineLabel.setText(item.getDeadline().toString());
}
public void deleteItem(ToDoItem item) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Delete Todo Item");
alert.setHeaderText("Delete item: " + item.getShortDescription());
alert.setContentText("Are you sure? Press OK to confirm or cancel to Back out.");
Optional<ButtonType> result = alert.showAndWait();
if(result.isPresent() && (result.get() == ButtonType.OK)) {
ToDoData.getInstance().deleteTodoItem(item);
}
}
public void completedItem(ToDoItem item) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Mark as Completed");
alert.setHeaderText("Completed task: " + item.getShortDescription());
alert.setContentText("Are you sure? Press OK to confirm or cancel to Back out.");
Optional<ButtonType> result = alert.showAndWait();
if(result.isPresent() && (result.get() == ButtonType.OK)) {
ToDoData.getInstance().completed(item);
}
}
#FXML
public void wantTodaysItems(){
ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem();
filteredList.setPredicate(wantTodaysItems);
if(filteredList.isEmpty()) {
itemDetailsTextArea.clear();
deadlineLabel.setText("");
} else if(filteredList.contains(selectedItem)) {
todoListView.getSelectionModel().select(selectedItem);
} else {
todoListView.getSelectionModel().selectFirst();
}
}
#FXML
public void wantAllItems(){
ToDoItem selectedItem = todoListView.getSelectionModel().getSelectedItem();
filteredList.setPredicate(wantAllItems);
if(filteredList.isEmpty()) {
itemDetailsTextArea.clear();
deadlineLabel.setText("");
} else if(filteredList.contains(selectedItem)) {
todoListView.getSelectionModel().select(selectedItem);
} else {
todoListView.getSelectionModel().selectFirst();
}
}
#FXML
public void handleExit() {
Platform.exit();
}
}
ToDoData class is:
package todolist;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class ToDoData {
private static ToDoData instance = new ToDoData();
private static String filename = "TodoListItems.txt";
private ObservableList<ToDoItem> todoItems;
private ObservableList<ToDoItem> completed;
private DateTimeFormatter formatter;
public static ToDoData getInstance() {
return instance;
}
private ToDoData() {
formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
}
public ObservableList<ToDoItem> getTodoItems() {
return todoItems;
}
public void addTodoItem(ToDoItem item) {
todoItems.add(item);
}
public void loadTodoItems() throws IOException {
todoItems = FXCollections.observableArrayList();
Path path = Paths.get(filename);
BufferedReader br = Files.newBufferedReader(path);
String input;
try {
while ((input = br.readLine()) != null) {
String[] itemPieces = input.split("\t");
String shortDescription = itemPieces[0];
String details = itemPieces[1];
String dateString = itemPieces[2];
LocalDate date = LocalDate.parse(dateString, formatter);
ToDoItem todoItem = new ToDoItem(shortDescription, details, date);
todoItems.add(todoItem);
}
} finally {
if(br != null) {
br.close();
}
}
}
public void storeTodoItems() throws IOException {
Path path = Paths.get(filename);
BufferedWriter bw = Files.newBufferedWriter(path);
try {
Iterator<ToDoItem> iter = todoItems.iterator();
while(iter.hasNext()) {
ToDoItem item = iter.next();
bw.write(String.format("%s\t%s\t%s",
item.getShortDescription(),
item.getDetails(),
item.getDeadline().format(formatter)));
bw.newLine();
}
} finally {
if(bw != null) {
bw.close();
}
}
}
public void deleteTodoItem(ToDoItem item) {
todoItems.remove(item);
}
public void completed(ToDoItem item){
completed.add(item);
}
public ObservableList <ToDoItem> completed(){
return completed;
}
}
todoItem class is:
package todolist;
import java.time.LocalDate;
public class ToDoItem {
private String shortDescription;
private String details;
private LocalDate deadline;
public ToDoItem(String shortDescription, String details, LocalDate deadline) {
this.shortDescription = shortDescription;
this.details = details;
this.deadline = deadline;
}
public String getShortDescription() {
return shortDescription;
}
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public LocalDate getDeadline() {
return deadline;
}
public void setDeadline(LocalDate deadline) {
this.deadline = deadline;
}
}
This answer is assuming I understand you correctly. In this example, I use a FilteredList on the TableView, and I used the FilteredList's Predicate to show the data in the TableView based on the Button that is pressed. I altered the answer from here.
KeyCode 1
This code allows the TableView to update when the CheckBox values changes.
todoItem -> new Observable[] { todoItem.completeProperty()}
Key Code 2
This code allows the FilteredList to set the TableView's data based on the Button that is pressed.
flToDoItems.setPredicate((todoItem) -> {
return todoItem.getComplete();
});
Main
import java.time.LocalDate;
import java.util.Arrays;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application
{
final TableView<ToDoItem> selectionTableView = new TableView<>();
private final ObservableList<ToDoItem> toDoItems = FXCollections.observableList(
Arrays.asList(
new ToDoItem("Task 1", "This is Task 1", LocalDate.now().plusMonths(1)),
new ToDoItem("Task 2", "This is Task 2", LocalDate.now().plusMonths(2)),
new ToDoItem("Task 3", "This is Task 3", LocalDate.now().plusMonths(3)),
new ToDoItem("Task 4", "This is Task 4", LocalDate.now().plusMonths(4)),
new ToDoItem("Task 5", "This is Task 5", LocalDate.now().plusMonths(5))
),
todoItem -> new Observable[] { todoItem.completeProperty()}//This is needed for automatic updates in the TableView!!!!
);
final FilteredList<ToDoItem> flToDoItems = new FilteredList(toDoItems, p -> true);
public static void main(String[] args)
{
launch();
}
#Override
public void start(Stage stage)
{
final TableView<ToDoItem> todoListSelectionTableView = createTodoListSelectionTableView();
todoListSelectionTableView.setItems(flToDoItems);
Button btnAllTasks = new Button("All");
btnAllTasks.setOnAction((t) -> {
flToDoItems.setPredicate((todoItem) -> {
return true;
});
});
Button btnCompleteTasks = new Button("Complete");
btnCompleteTasks.setOnAction((t) -> {
flToDoItems.setPredicate((todoItem) -> {
return todoItem.getComplete();
});
});
Button btnIncompleteTask = new Button("Incomplete");
btnIncompleteTask.setOnAction((t) -> {
flToDoItems.setPredicate((todoItem) -> {
return !todoItem.getComplete();
});
});
VBox root = new VBox(new HBox(btnAllTasks, btnIncompleteTask, btnCompleteTasks), todoListSelectionTableView);
Scene scene = new Scene(root, 500, 500);
stage.setScene(scene);
stage.show();
}
private TableView<ToDoItem> createTodoListSelectionTableView() {
selectionTableView.setPrefSize(440, 180);
TableColumn<ToDoItem, String> shortDescriptionColumn = new TableColumn<>("Short Description");
shortDescriptionColumn.setCellValueFactory(cd -> cd.getValue().shortDescriptionProperty());
selectionTableView.getColumns().add(shortDescriptionColumn);
TableColumn<ToDoItem, String> detailsColumn = new TableColumn<>("Details");
detailsColumn.setCellValueFactory(cd -> cd.getValue().detailsProperty());
selectionTableView.getColumns().add(detailsColumn);
TableColumn<ToDoItem, LocalDate> deadlineColumn = new TableColumn<>("Deadline");
deadlineColumn.setCellValueFactory(cd -> cd.getValue().deadlineProperty());
selectionTableView.getColumns().add(deadlineColumn);
TableColumn<ToDoItem, Boolean> completeColumn = new TableColumn<>("Complete");
completeColumn.setCellValueFactory(cd -> cd.getValue().completeProperty());
completeColumn.setCellFactory(CheckBoxTableCell.forTableColumn(completeColumn));
selectionTableView.getColumns().add(completeColumn);
selectionTableView.setEditable(true);
return selectionTableView;
}
}
ToDoItem
import java.time.LocalDate;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class ToDoItem {
private final StringProperty shortDescription;
private final StringProperty details;
private final ObjectProperty<LocalDate> deadline;
private final BooleanProperty complete;
public ToDoItem(String shortDescription, String details, LocalDate deadline) {
this.shortDescription = new SimpleStringProperty(shortDescription);
this.details = new SimpleStringProperty(details);
this.deadline = new SimpleObjectProperty(deadline);
this.complete = new SimpleBooleanProperty(false);
}
public String getShortDescription() {
return shortDescription.get();
}
public void setShortDescription(String shortDescription) {
this.shortDescription.set(shortDescription);
}
public StringProperty shortDescriptionProperty()
{
return this.shortDescription;
}
public String getDetails() {
return details.get();
}
public void setDetails(String details) {
this.details.set(details);
}
public StringProperty detailsProperty()
{
return this.details;
}
public LocalDate getDeadline() {
return deadline.get();
}
public void setDeadline(LocalDate deadline) {
this.deadline.set(deadline);
}
public ObjectProperty<LocalDate> deadlineProperty()
{
return this.deadline;
}
public void setComplete(boolean complete)
{
this.complete.set(complete);
}
public boolean getComplete()
{
return this.complete.get();
}
public BooleanProperty completeProperty()
{
return this.complete;
}
}
Output
I have written this little example application:
package application;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
public Person(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public String getFirstName() {
return firstName.get();
}
public String getLastName() {
return lastName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public StringProperty lastNameProperty() {
return lastName;
}
}
#Override
public void start(Stage primaryStage) {
try {
StackPane root = new StackPane();
TableView<Person> tv = new TableView<>();
TableColumn<Person, String> col = new TableColumn<Person, String>("FirstName");
col.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
tv.getColumns().add(col);
tv.setEditable(true);
col = new TableColumn<Person, String>("LastName");
col.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
col.setCellFactory(TextFieldTableCell.forTableColumn());
col.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
#Override
public void handle(CellEditEvent<Person, String> event) {
System.out.println(tv.getItems().get(1).getLastName());
}
});
tv.getColumns().add(col);
for (int i = 0; i < 30; i++) {
tv.getItems().add(new Person("Test" + i, "Test" + i));
}
root.getChildren().add(tv);
Scene scene = new Scene(root, 400, 200);
primaryStage.setScene(scene);
primaryStage.show();
tv.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
// ...
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I want to perform action when the ScrollBar has reached the bottom. Then I want to reload more datas from the database. But only then, when the user has seen all the already loaded datas (= scrollbar on the bottom). Do you have nice suggestions to solve this issue?
My first idea was to catch the MOUSE_RELEASED event (when the users drags the bar) of the TableView and then to check the position of the ScrollBar:
- getValue() gets the position of the bar
- getMax() the maximum value (=bottom).
But I can't find a way (without using the css-selector via this method) to get the ScrollBar from a given TableView. So I can't check the position of it in a certain TableView.
Do you have any ideas??
I am excited. Thanks for your help.
The only way to get the scroll bar is via a lookup, which is a bit of a hack, but it will work as long as you do it after the table has been rendered on the scene. You need
ScrollBar verticalBar = (ScrollBar) table.lookup(".scroll-bar:vertical");
Note that there's no need to mess with user events: you can just observe the scroll bar's value property directly:
verticalBar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue.doubleValue() >= verticalBar.getMax()) {
// add more data...
}
});
SSCCE:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class AddMoreTableDataOnScrollToBottom extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Value", Item::valueProperty));
addMoreData(table, 20);
Scene scene = new Scene(new BorderPane(table), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
ScrollBar verticalBar = (ScrollBar) table.lookup(".scroll-bar:vertical");
verticalBar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue.doubleValue() >= verticalBar.getMax()) {
addMoreData(table, 20);
}
});
}
private void addMoreData(TableView<Item> table, int numItems) {
Task<List<Item>> dataRetrieveTask = new Task<List<Item>>() {
#Override
public List<Item> call() throws Exception {
// mimic connect to db:
Thread.sleep(500);
List<Item> items = new ArrayList<>();
int nextItem = table.getItems().size() + 1 ;
for (int i = nextItem; i < nextItem + numItems; i++ ){
items.add(new Item("Item "+i, i));
}
return items ;
}
};
dataRetrieveTask.setOnSucceeded(e -> table.getItems().addAll(dataRetrieveTask.getValue()));
new Thread(dataRetrieveTask).start();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> prop) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
I'm using the following code from Oracle:
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.VBox;
public class TreeViewSample extends Application {
List<Employee> employees = Arrays.<Employee>asList(
new Employee("Ethan Williams", "Sales Department"),
new Employee("Emma Jones", "Sales Department"),
new Employee("Michael Brown", "Sales Department"),
new Employee("Anna Black", "Sales Department"),
new Employee("Rodger York", "Sales Department"),
new Employee("Susan Collins", "Sales Department"),
new Employee("Mike Graham", "IT Support"),
new Employee("Judy Mayer", "IT Support"),
new Employee("Gregory Smith", "IT Support"),
new Employee("Jacob Smith", "Accounts Department"),
new Employee("Isabella Johnson", "Accounts Department"));
TreeItem<String> rootNode =
new TreeItem<String>("MyCompany Human Resources");
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
rootNode.setExpanded(true);
for (Employee employee : employees) {
TreeItem<String> empLeaf = new TreeItem<String>(employee.getName());
boolean found = false;
for (TreeItem<String> depNode : rootNode.getChildren()) {
if (depNode.getValue().contentEquals(employee.getDepartment())){
depNode.getChildren().add(empLeaf);
found = true;
break;
}
}
if (!found) {
TreeItem depNode = new TreeItem(employee.getDepartment());
rootNode.getChildren().add(depNode);
depNode.getChildren().add(empLeaf);
}
}
stage.setTitle("Tree View Sample");
VBox box = new VBox();
final Scene scene = new Scene(box, 400, 300);
scene.setFill(Color.LIGHTGRAY);
TreeView<String> treeView = new TreeView<String>(rootNode);
treeView.setEditable(true);
treeView.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>(){
#Override
public TreeCell<String> call(TreeView<String> p) {
return new TextFieldTreeCellImpl();
}
});
box.getChildren().add(treeView);
stage.setScene(scene);
stage.show();
}
private final class TextFieldTreeCellImpl extends TreeCell<String> {
private TextField textField;
private ContextMenu addMenu = new ContextMenu();
public TextFieldTreeCellImpl() {
MenuItem addMenuItem = new MenuItem("Add Employee");
addMenu.getItems().add(addMenuItem);
addMenuItem.setOnAction(new EventHandler() {
public void handle(Event t) {
TreeItem newEmployee =
new TreeItem<String>("New Employee");
getTreeItem().getChildren().add(newEmployee);
}
});
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
setGraphic(getTreeItem().getGraphic());
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getTreeItem().getGraphic());
if (
!getTreeItem().isLeaf()&&getTreeItem().getParent()!= null
){
setContextMenu(addMenu);
}
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
public static class Employee {
private final SimpleStringProperty name;
private final SimpleStringProperty department;
private Employee(String name, String department) {
this.name = new SimpleStringProperty(name);
this.department = new SimpleStringProperty(department);
}
public String getName() {
return name.get();
}
public void setName(String fName) {
name.set(fName);
}
public String getDepartment() {
return department.get();
}
public void setDepartment(String fName) {
department.set(fName);
}
}
}
This code produces a GUI with a very basic, editable TreeView. However, when clicking around the cells that populate the tree, eventually, the text fields used for editing will begin to display incorrect information (information that is contained within the tree, but that is not represented by the cell being edited). I do not understand why this is happening, and I haven't found any reference of this happening anywhere on Google or elsewhere on StackOverflow. If anyone could help me understand why this is occuring, I'd be very happy.
Thanks!
I checked your application and I have found only one weird thing. When you create the edit textfield once, the unsaved information will be visible in that textfield later.
This modification will solve it:
# Override
public void cancelEdit() {
super.cancelEdit();
this.setText(this.getItem());
this.textField.setText(this.getItem());
this.setGraphic(this.getTreeItem().getGraphic());
}
I'm trying to put a SimpleStringProperty on a TableView this is the class im using:
package com.launch.history;
import java.net.URL;
import java.util.ResourceBundle;
import com.launch.WebController;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
public class HistoryController implements Initializable {
#FXML
private TableView<HistoryClient> tableView;
#FXML
private TableColumn<HistoryClient, String> tableColumn;
#Override
public void initialize(URL location, ResourceBundle resources) {
setTableView();
}
public void setColumns() {
tableColumn.setCellValueFactory(new PropertyValueFactory<HistoryClient, String>("history"));
}
public void setTableView() {
tableView.setItems(getHistory());
setColumns();
tableView.setOnMouseClicked(new EventHandler<MouseEvent>() {
#SuppressWarnings("static-access")
#Override
public void handle(MouseEvent event) {
if(tableView.getSelectionModel().getSelectedIndex() >= 0) {
HistoryClient link = tableView.getSelectionModel().getSelectedItem();
WebController.engine.load(link.getHistory());
}
}
});
}
public ObservableList<HistoryClient> getHistory() {
ObservableList<HistoryClient> data = FXCollections.observableArrayList();
data.addAll(new HistoryClient());
return data;
}
}
And this is the the stuff about history:
public static String getHistory() {
return history.get();
}
public static void setHistory(String history) {
HistoryClient.history.set(history);
}
private static SimpleStringProperty history = new SimpleStringProperty();
But i only get 1 line on my TableView(The last link)
And this is how i set the value of history:
public static void readHistory() {
if(Files.HISTORY_FILE.exists()) {
try(BufferedReader in = new BufferedReader(new FileReader(Files.HISTORY_FILE))) {
String line;
while( (line = in.readLine()) != null) {
history.set(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
That works fine i printed out history and it was the same as in the file so i really don't see the problem because last time when i used TableView i kinda did the same thing.
And yes i did set the fx:id in scene builder.