JavaFX - 使自定义对象的ListView可编辑吗?

时间:2018-01-11 10:03:15

标签: java listview user-interface javafx

所以我使用JavaFX创建了一个简单的待办事项列表。我希望通过简单地双击它并能够为该项目输入新值来使待办事项列表中的每个项目都可编辑。我一直在阅读JavaDocs,以便更好地理解如何做到这一点。我在网上找到了一个关于如何使用String类型列表视图执行此操作的示例,但是当您拥有自定义对象的列表视图时,它似乎更复杂(就像我的情况一样,ListView的类型为ToDoItem,a这篇文章后面的课程)。这是我到目前为止所尝试过的。

  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());
            }
        });

    }

我的ListView是ToDoItem类型。这是ToDoItem类的样子:

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();
}
}

待办事项列表UI本身在屏幕上运行并显示没有问题,并且所有其他功能都有效(添加项目,删除项目等...)。我唯一遇到麻烦的就是让它可以编辑。

这是我按下ENTER时得到的堆栈跟踪(第162行引用setOnEditCommit方法的开头,第165行引用handle方法中的单行代码):

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)

1 个答案:

答案 0 :(得分:1)

将列表视图上的单元格工厂设置为提供支持编辑的单元格的单元工厂:

    todoListView.setCellFactory(new Callback<ListView<TodoItem>, ListCell<TodoItem>>() {
        @Override
        public ListCell<TodoItem> call(ListView<TodoItem> lv) {
            TextFieldListCell<TodoItem> cell = new TextFieldListCell<TodoItem>();
            // ...

            return cell ;
        }
    });

然后你几乎立即将其替换为提供不可编辑单元格的单元工厂:

    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;
        }
    });

因此,当然,列表视图中的单元格不可编辑。

由于列表视图(最多)每个项目都有一个列表单元格,因此您需要在单个单元格中提供所需的所有功能:

    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;
        }
    });

另请注意,您的updateItem()方法需要处理所有情况(例如,如果项目从明天到期的项目更改为非项目,则需要从单元格中移除颜色&#39; s文本)。

当前实现onEditCommit的方式,您将获得空指针异常,因为单元格实现没有处理截止时间为空(但onEditCommit处理程序将截止时间设置为null )。我将为您留下,因为我不知道您在该部分代码中打算做什么。