Issue
I'm writing a simple task manager app in JavaFX. below is the simple code I've written so far
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
class TaskClass {
public String task_Title;
public String task_Duration;
public String Remained_Days;
public boolean task_Is_Completed;
}
@Override
public void start(Stage primaryStage) {
TaskClass TaskClassObject = new TaskClass();
TaskClassObject.task_Title = "Buy a Car";
TaskClassObject.task_Duration = "7 days";
TaskClassObject.Remained_Days = "2 days";
TaskClassObject.task_Is_Completed = false;
TreeView<TaskClass> treeView = new TreeView<TaskClass>();
CheckBoxTreeItem<TaskClass> rootNode = new CheckBoxTreeItem<TaskClass>(TaskClassObject);
treeView.setRoot(rootNode);
treeView.setCellFactory(CheckBoxTreeCell.<TaskClass>forTreeView());
VBox vb = new VBox();
VBox.setVgrow(treeView, Priority.ALWAYS);
vb.getChildren().add(treeView);
Scene Scene = new Scene(vb, 800, 600);
primaryStage.setTitle("my task manager app");
primaryStage.setScene(Scene);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
I want to iterate over treeView
nodes and get the TaskClassObject
for each node and access its fields (TaskClassObject.task_Title
and other fields) and checkbox gets checked based on TaskClassObject.task_Is_Completed
. if its true, checkbox in node should be checked.
Solution
Clarifying info on approach
You state:
I want to iterate over treeView nodes and get the TaskClassObject for each node and access its fields (TaskClassObject.task_Title and other fields) and checkbox gets checked based on TaskClassObject.task_Is_Completed. if its true, checkbox in node should be checked.
However, that is not what you really want to do. You don't want to try to iterate over things and worry about nodes and stuff in a virtualized control like a TreeView.
GUI programming is event-driven, what you want to do is respond to events and update the associated model state based upon that.
What you actually need to ask (and want) is this:
How do I bind the selected value from a CheckBoxTreeItem to my model class, so that when a CheckBox in an associated CheckBoxTreeCell is selected or unselected my associated property in my model class is updated to reflect the new value?
(I updated your question title to a subset of this so others could easily find it if they have the same issue).
How to achieve the required binding
The CheckBoxTreeItem
and CheckBoxTreeCell
classes are fairly high-level classes that are set up to respond to appropriate events and update model states based upon them. You need to make use of the high-level interfaces they provide to make them actually usable. To do this for your example, you need to:
Make your model class (the
Task
) use and expose JavaFX properties so that the UI can automatically update based on the property values and updates to the UI can automatically update the property values.Bind the
complete
property in your task instance to the associatedCheckBoxTreeItem
. This is done via a bi-directional bind so that updates to the complete property inTask
will update the selection state in the UI and vice-versa.task.completeProperty().bindBidirectional(treeItem.selectedProperty());
Provide a selected property binding callback to the
CheckBoxTreeCell
to associate it with yourCheckBoxTreeItem
. This actually normally happens by default, but, if you also want a StringConverter (which you do in this case) then you need to supply one manually as far as I can tell.Provide a StringConverter to the
CheckBoxTreeCell
so that it can display yourTask
in a legible way.
Example Code
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public class App extends Application {
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) {
Task task = createTask();
CheckBoxTreeItem<Task> treeItem = new CheckBoxTreeItem<>(task);
task.completeProperty().bindBidirectional(treeItem.selectedProperty());
TreeView<Task> treeView = new TreeView<>();
attachTaskCellFactory(treeView);
treeView.setRoot(treeItem);
StackPane layout = new StackPane(treeView);
Scene Scene = new Scene(layout);
stage.setScene(Scene);
stage.show();
}
private void attachTaskCellFactory(TreeView<Task> treeView) {
Callback<TreeItem<Task>, ObservableValue<Boolean>> getSelectedProperty =
item -> {
if (item instanceof CheckBoxTreeItem<?>) {
return ((CheckBoxTreeItem<?>)item).selectedProperty();
}
return null;
};
StringConverter<TreeItem<Task>> treeItemStringConverter = new StringConverter<>() {
@Override
public String toString(TreeItem<Task> treeItem) {
return treeItem == null || treeItem.getValue() == null || treeItem.getValue().getTitle() == null
? ""
: treeItem.getValue().getTitle();
}
@Override
public TreeItem<Task> fromString(String string) {
// not actually used, only provided to allow a concrete instance to be created.
return null;
}
};
treeView.setCellFactory(
CheckBoxTreeCell.forTreeView(
getSelectedProperty,
treeItemStringConverter
)
);
}
private Task createTask() {
Task task = new Task(
"Buy a Car",
Duration.of(7, ChronoUnit.DAYS),
Duration.of(2, ChronoUnit.DAYS),
false
);
task.completeProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Task complete: " + newValue);
});
return task;
}
public class Task {
final private StringProperty title = new SimpleStringProperty();
final private ObjectProperty<Duration> duration = new SimpleObjectProperty<>();
final private ObjectProperty<Duration> remainingDays = new SimpleObjectProperty<>();
final private BooleanProperty complete = new SimpleBooleanProperty();
public Task(String title, Duration duration, Duration remainingDays, Boolean complete) {
setTitle(title);
setDuration(duration);
setRemainingDays(remainingDays);
setComplete(complete);
}
public String getTitle() {
return title.get();
}
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public Duration getDuration() {
return duration.get();
}
public ObjectProperty<Duration> durationProperty() {
return duration;
}
public void setDuration(Duration duration) {
this.duration.set(duration);
}
public Duration getRemainingDays() {
return remainingDays.get();
}
public ObjectProperty<Duration> remainingDaysProperty() {
return remainingDays;
}
public void setRemainingDays(Duration remainingDays) {
this.remainingDays.set(remainingDays);
}
public boolean isComplete() {
return complete.get();
}
public BooleanProperty completeProperty() {
return complete;
}
public void setComplete(boolean complete) {
this.complete.set(complete);
}
}
}
Answered By - jewelsea
Answer Checked By - Robin (JavaFixing Admin)