【问题标题】:JavaFX - Making ListView of custom made objects editable?JavaFX - 使自定义对象的 ListView 可编辑?
【发布时间】:2018-06-20 14:53:39
【问题描述】:

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

  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 行指的是句柄方法中的单行代码):

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)

【问题讨论】:

  • 您的代码对我来说工作得很好,完全一样(除了删除您评论的行是不正确的)。实际问题是什么?你能解释一下正在发生或没有发生的事情与你所期望的不同,并扩展代码(应该只需要多几行)以形成minimal reproducible example吗?
  • “工作正常”是指它是可编辑的,并且编辑会以您定义的方式传播回支持列表。
  • 当我删除给出错误的行时,它运行正常。如中,GUI 待办事项列表窗口打开显示所有待办事项,它们是可点击的,显示描述,上下文菜单工作正常等。但是,它对我来说不可编辑。双击列表视图中的项目不会执行任何操作。我无法理解您是如何编辑列表视图的。
  • 我已经编辑了我的初始帖子以展示我的整个初始化方法。我已经评论了我试图使列表视图可编辑的代码部分。
  • 呃,你将提供编辑的单元工厂替换为另一个不可编辑的单元工厂。

标签: java listview user-interface javafx


【解决方案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() 方法需要处理所有情况(例如,如果项目从明天到期的更改为明天到期的,则需要从单元格的文本中删除颜色)。

当前实现onEditCommit 的方式,您将得到空指针异常,因为单元实现不处理为空的截止日期(但onEditCommit 处理程序将截止日期设置为空)。我将把它留给你,因为我不知道你在这部分代码中真正打算做什么。

【讨论】:

  • 感谢您的帮助。我进行了必要的更改,现在可以编辑单元格。但是,当我按 Enter 提交更改时,会发生一些奇怪的事情:抛出 java.lang.UnsupportedOperationException,但单元格仍保持 TextField 形式。当我单击其他地方失去焦点时,它会变回 ListCell 并应用我所做的更改。如何允许 ENTER 键提交更改而不引发异常?
  • 我在我的原始帖子中添加了使用 ENTER 键提交的尝试。我使用了 setOnKeyPressed 方法。仍然以同样的方式抛出异常。
  • @arslansana 我在答案的最后一段中提到了这一点
  • 如果您查看我的 OP,您会看到我修改了 fromString 方法,因此它不再传递 null 来创建对象,所以我认为这不是问题所在。我得到的异常不是 nullPointer 异常,而是 UnsupportedOperationException
  • @arslansana 是的,我没有注意到这种变化。尝试删除 onEditCommit 处理程序(该更改不需要它)。如果这不起作用,请将完整的堆栈跟踪添加到问题中。
猜你喜欢
  • 2016-08-08
  • 2017-05-24
  • 2016-07-25
  • 1970-01-01
  • 2014-10-10
  • 1970-01-01
  • 2018-09-08
相关资源
最近更新 更多