Reputation: 111
So I've made a simple to-do list using JavaFX. I wish to make each item in the To-do list editable by simply double clicking it and being able to enter new values for the item. I've been reading through JavaDocs to gain a better understanding of how to do this. I found an example online on how to do this with A listview of type String, but it seems to be more complicated when you have a listview of custom made objects (as is the case with me, where the ListView is of type ToDoItem, a class shown later in this post). Here is what I've tried so far.
public void initialize()
{
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);
}
});
listContextMenu.getItems().addAll(deleteMenuItem);
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 dd, yyyy");
deadlineLabel.setText(item.getDeadline().format(df));
}
}
});
wantAllItems = new Predicate<TodoItem>() {
@Override
public boolean test(TodoItem todoItem) {
return true;
}
};
wantTodaysItems = new Predicate<TodoItem>() {
@Override
public boolean test(TodoItem todoItem) {
return todoItem.getDeadline().equals(LocalDate.now());
}
};
filteredList = new FilteredList<TodoItem>(TodoData.getInstance().getTodoitems(), wantAllItems);
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(TodoData.getInstance().getTodoitems());
todoListView.setItems(sortedList);
todoListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
todoListView.getSelectionModel().selectFirst();
todoListView.setEditable(true);
todoListView.setCellFactory(new Callback<ListView<TodoItem>, ListCell<TodoItem>>() {
@Override
public ListCell<TodoItem> call(ListView<TodoItem> lv) {
TextFieldListCell<TodoItem> cell = new TextFieldListCell<TodoItem>(){
@Override
public 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 if(item.getDeadline().equals(LocalDate.now().plusDays(1)))
setTextFill(Color.BROWN);
}
}
@Override
public void commitEdit(TodoItem newValue) {
super.commitEdit(newValue);
}
};
cell.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.ENTER)
cell.commitEdit(lv.getSelectionModel().getSelectedItem());
}
});
cell.emptyProperty().addListener(
(obs, wasEmpty, isNowEmpty) ->
{
if(isNowEmpty)
cell.setContextMenu(null);
else
cell.setContextMenu(listContextMenu);
}
);
cell.setConverter(new StringConverter<TodoItem>() {
@Override
public String toString(TodoItem object) {
return object.toString();
}
@Override
public TodoItem fromString(String string) {
cell.getItem().setShortDescription(string);
return cell.getItem();
}
});
return cell;
}
});
// this is the method where the source of the exception is being reported
todoListView.setOnEditCommit(new EventHandler<ListView.EditEvent<TodoItem>>() {
@Override
public void handle(ListView.EditEvent<TodoItem> e) {
todoListView.getItems().set(e.getIndex(), e.getNewValue());
}
});
}
My ListView is of type ToDoItem. Here is what ToDoItem class looks like:
public class TodoItem {
private SimpleStringProperty shortDescription;
private SimpleStringProperty details;
private LocalDate deadline;
public TodoItem(String shortDescription, String details, LocalDate deadline) {
this.shortDescription = new SimpleStringProperty(shortDescription);
this.details = new SimpleStringProperty(details);
this.deadline = deadline;
}
public String getShortDescription() {
return shortDescription.get();
}
public void setShortDescription(String shortDescription) {
this.shortDescription.set(shortDescription);
}
public String getDetails() {
return details.get();
}
public void setDetails(String details) { this.details.set(details); }
public LocalDate getDeadline() {
return deadline;
}
public void setDeadline(LocalDate deadline) {
this.deadline = deadline;
}
@Override
public String toString() {
return shortDescription.get();
}
}
The To-Do list UI itself runs and shows on screen without a problem, and all other functionality works (adding item, deleting item etc..). The only thing im having trouble with is making it editable.
Here is the stack trace I get upon pressing ENTER (line 162 refers to the beginning of the setOnEditCommit method and line 165 refers to the single line of code within the handle method):
Exception in thread "JavaFX Application Thread" java.lang.UnsupportedOperationException
at java.util.AbstractList.set(AbstractList.java:132)
at com.arslansana.todolist.Controller$7.handle(Controller.java:165)
at com.arslansana.todolist.Controller$7.handle(Controller.java:162)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at javafx.scene.control.ListCell.commitEdit(ListCell.java:378)
at com.arslansana.todolist.Controller$6$1.commitEdit(Controller.java:122)
at com.arslansana.todolist.Controller$6$1.commitEdit(Controller.java:104)
at javafx.scene.control.cell.CellUtils.lambda$createTextField$615(CellUtils.java:248)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Node.fireEvent(Node.java:8411)
at com.sun.javafx.scene.control.behavior.TextFieldBehavior.fire(TextFieldBehavior.java:179)
at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:178)
at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:216)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:148)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:247)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:246)
at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
at com.sun.glass.ui.View.notifyKey(View.java:966)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Upvotes: 1
Views: 1564
Reputation: 209418
You set the cell factory on the list view to a cell factory that provides cells that support editing:
todoListView.setCellFactory(new Callback<ListView<TodoItem>, ListCell<TodoItem>>() {
@Override
public ListCell<TodoItem> call(ListView<TodoItem> lv) {
TextFieldListCell<TodoItem> cell = new TextFieldListCell<TodoItem>();
// ...
return cell ;
}
});
but then you almost immediately replace that with a cell factory that provides non-editable cells:
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);
// ...
}
};
// ...
return cell;
}
});
So, of course, the cells in your list view are not editable.
Since a list view has (at most) a single list cell for each item, you need to provide all the functionality you want in a single cell:
todoListView.setCellFactory(new Callback<ListView<TodoItem>, ListCell<TodoItem>>() {
@Override
public ListCell<TodoItem> call(ListView<TodoItem> lv) {
TextFieldListCell<TodoItem> cell = new TextFieldListCell<TodoItem>(){
@Override
protected void updateItem(TodoItem item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setStyle("");
} else
if(item.getDeadline().isBefore(LocalDate.now().plusDays(1)))
setStyle("-fx-text-fill: red ;");
else if(item.getDeadline().equals(LocalDate.now().plusDays(1)))
setStyle("-fx-text-fill: brown;");
else
setStyle("");
}
};
cell.setConverter(new StringConverter<TodoItem>() {
@Override
public String toString(TodoItem object) {
return object.toString();
}
@Override
public TodoItem fromString(String string) {
return new TodoItem(string, null, null);
}
});
return cell;
}
});
Note also that your updateItem()
method needs to handle all cases (e.g. if the item changes from one that is due tomorrow to one that is not, then the color needs to be removed from the cell's text).
The way your onEditCommit
is currently implemented, you will get null pointer exceptions as the cell implementation doesn't deal with the deadline being null (but the onEditCommit
handler sets the deadline to null). I'll leave that for you as I don't know what you really intend to do in that part of the code.
Upvotes: 3