具有可编辑 ListView 的 ListCell 作为图形:commitEdit 上的 ClassCastException

ListCell with editable ListView as graphic: ClassCastException on commitEdit

在 JavaFx v17、JDK v17、Maven、IntelliJ 中:
Listview (=TOKListView) 在 Listview (=SIGListView) 的每一行中。当我编辑 TOKListView 的单元格时,我在 startEdit() 部分调用了 commitEdit(),然后我收到错误 class TokenWord cannot be cast to class Signification。 SIGListView 不应该,但会执行 updateItem(),但为什么呢?

Main.class // ReprEx

public class Main extends Application {

    public static void main(String[] args) {
        Application.launch(Main.class);
    }

    public class Signification {
        ArrayList<TokenWord> tokenWords;

        public Signification(ArrayList<TokenWord> tokenWords) {
            this.tokenWords = tokenWords;
        }

        public ArrayList<TokenWord> getTokenWords() {
            return tokenWords;
        }
    }

    public class TokenWord {
        String Text;

        public TokenWord(String text) {
            Text = text;
        }

        public String getText() {
            return Text;
        }
    }

    final ListView<Signification> significationListView = new ListView<>();

    final ArrayList<Signification> significations = new ArrayList<>();

    @Override
    public void start(Stage stage) {

        significations.add(
                new Signification(new ArrayList<TokenWord>(Arrays.asList(
                        new TokenWord("One")
                ))));


        significationListView.setCellFactory(significationListView -> {
            ListCell<Signification> siCell = new ListCell<Signification>() {

                //PROBLEM AREA ---- START --------------------------------------
                //########## PROBLEM CONSUMED (of PROBLEM FIRED see below) #########
                @Override
                public void updateItem(Signification sigItem, boolean empty) {
                    super.updateItem(sigItem, empty);
                    if ((!empty) && (sigItem != null)) {

                        ListView<TokenWord> tokenWordListView = new ListView<TokenWord>();
                        tokenWordListView.setEditable(true);
                        tokenWordListView.setOrientation(Orientation.HORIZONTAL);

                        tokenWordListView.setCellFactory(twListView -> {

                            //  each Cell a new Listview
                            ListCell<TokenWord> twCell = new ListCell<TokenWord>() {

                                @Override
                                public void updateItem(TokenWord twItem, boolean empty) {
                                    super.updateItem(twItem, empty);
                                    if ((!empty) && (twItem != null)) setText(twItem.getText());
                                }

                                @Override
                                public void startEdit() {
                                    if (!isEditable() || !getListView().isEditable()) return;
                                    super.startEdit();
                                    if (isEditing()) {
                                        setText("");
                                        TextField textField = new TextField(getItem().getText());
                                        textField.setOnAction(actionEvent -> {

                                            //########## PROBLEM FIRED ###########
                                            commitEdit(new TokenWord(textField.getText()));
                                        });
                                        setGraphic(textField);
                                    } else setGraphic(null);
                                }
                            };

                            return twCell;
                        });
                        tokenWordListView.getItems().addAll(sigItem.getTokenWords());
                        setGraphic(tokenWordListView);
                    } else {
                        setGraphic(null);
                    }
                }
                //PROBLEM AREA ---- END --------------------------------------

            };

            siCell.setPrefHeight(40);

            return siCell;
        });

        significationListView.getItems().addAll(significations);

        stage.setScene(new Scene(new StackPane(significationListView), 400, 150));

        stage.show();
    }
}

错误信息:

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class org.example.TheApp$TokenWord cannot be cast to class org.example.TheApp$Signification (org.example.TheApp$TokenWord and org.example.TheApp$Signification are in unnamed module of loader 'app')
    at org.example.TheApp.updateItem(TheApp.java:61)
    at javafx.scene.control.ListCell.updateItem(ListCell.java:481)
    at javafx.scene.control.ListCell.lambda$new(ListCell.java:168)
    at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
    at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
    at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
    at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:239)
    at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
    at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
    at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:211)
    at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:170)
    at javafx.scene.control.ListView.lambda$new(ListView.java:375)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    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:8792)
    at javafx.scene.control.ListCell.commitEdit(ListCell.java:390)
    at org.example.TheApp.lambda$startEdit[=11=](TheApp.java:94)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    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.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:8792)
    at com.sun.javafx.scene.control.behavior.TextFieldBehavior.fire(TextFieldBehavior.java:154)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.lambda$keyMapping(TextInputControlBehavior.java:332)
    at com.sun.javafx.scene.control.inputmap.InputMap.handle(InputMap.java:274)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    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.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:4105)
    at javafx.scene.Scene.processKeyEvent(Scene.java:2156)
    at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2630)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:218)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:150)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent(GlassViewEventHandler.java:250)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:249)
    at com.sun.glass.ui.View.handleKeyEvent(View.java:548)
    at com.sun.glass.ui.View.notifyKey(View.java:972)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$runLoop(WinApplication.java:184)
    at java.base/java.lang.Thread.run(Thread.java:833)
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class org.example.TheApp$TokenWord cannot be cast to class org.example.TheApp$Signification (org.example.TheApp$TokenWord and org.example.TheApp$Signification are in unnamed module of loader 'app')
    at org.example.TheApp.updateItem(TheApp.java:61)
    at javafx.scene.control.ListCell.updateItem(ListCell.java:481)
    at javafx.scene.control.ListCell.indexChanged(ListCell.java:337)
    at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:120)
    at javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1807)
    at javafx.scene.control.skin.VirtualFlow.addTrailingCells(VirtualFlow.java:2191)
    at javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1327)
    at javafx.scene.Parent.layout(Parent.java:1207)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Parent.layout(Parent.java:1214)
    at javafx.scene.Scene.doLayoutPass(Scene.java:579)
    at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2515)
    at com.sun.javafx.tk.Toolkit.lambda$runPulse(Toolkit.java:421)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:420)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:450)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:575)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:555)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:548)
    at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit(QuantumToolkit.java:353)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run$$$capture(InvokeLaterDispatcher.java:96)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$runLoop(WinApplication.java:184)
    at java.base/java.lang.Thread.run(Thread.java:833)

发生错误是因为编辑是通过事件控制的,我们有一个 ListView,其中的单元格包含一个 ListView:

  • 提交沿着通常的事件调度链传送
  • 提交从编辑单元格(包含文本字段的单元格)触发到内部 listView
  • 内部 listView 的默认提交处理程序通过保存编辑的值来处理事件
  • 然后它冒泡到外部 listView 的提交处理程序,在那里它导致 class 转换异常

最后一个发生是因为默认提交处理程序使用该事件。解决办法就是吃掉它。

一些代码(为清晰起见,提取到单独的 class,也可以内联完成)

public class OuterCell extends ListCell<Signification> {
    ListView<TokenWord> tokenWordListView;
    {
        tokenWordListView = new ListView<TokenWord>();
        tokenWordListView.setEditable(true);
        tokenWordListView.setOrientation(Orientation.HORIZONTAL);
        // grab default commit handler
        EventHandler<EditEvent<TokenWord>> handler = tokenWordListView.getOnEditCommit();
        tokenWordListView.setOnEditCommit(t -> {
            // let default handler save the edited value
            handler.handle(t);
            // consume to prevent dispatching the event to the outer list
            t.consume();
        });

        // use TextFieldListCell for inner cell 
        StringConverter<TokenWord> converter = new StringConverter<>() {

            @Override
            public String toString(TokenWord token) {
                return token != null ? token.getText() : "";
            }

            @Override
            public TokenWord fromString(String string) {
                return new TokenWord(string);
            }

        };
        tokenWordListView.setCellFactory(TextFieldListCell.forListView(converter));
        setPrefHeight(40);
    }

    @Override
    public void updateItem(Signification sigItem, boolean empty) {
        super.updateItem(sigItem, empty);
        if ((!empty) && (sigItem != null)) {
            tokenWordListView.getItems().setAll(sigItem.getTokenWords());
            setGraphic(tokenWordListView);
        } else {
            setGraphic(null);
        }
    }

}