Home > Enterprise >  ListCell with editable ListView as graphic: ClassCastException on commitEdit
ListCell with editable ListView as graphic: ClassCastException on commitEdit

Time:12-21

In JavaFx v17, JDK v17, Maven, IntelliJ:
A Listview<TokenWord> (=TOKListView) is in each row of a Listview<Signification> (=SIGListView). When I am editing a cell of the TOKListView I make a commitEdit() call in the startEdit() section, then I get an Error class TokenWord cannot be cast to class Signification. The SIGListView should not, but will do an updateItem(), but why?

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

Errormessage:

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$1.updateItem(TheApp.java:61)
    at javafx.scene.control.ListCell.updateItem(ListCell.java:481)
    at javafx.scene.control.ListCell.lambda$new$2(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$1(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$1$1.lambda$startEdit$0(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$62(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$1(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$3(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$1.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$2(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$11(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$3(WinApplication.java:184)
    at java.base/java.lang.Thread.run(Thread.java:833)

CodePudding user response:

The error occurs because editing is controlled via events and we have a ListView with a cell containing a ListView:

  • a commit is delivered along the usual event dispatch chain
  • the commit is fired from the editing cell (the one containing the textField) to the inner listView
  • the default commit handler of the inner listView handles the event by saving the edited value
  • then it bubbles up to the commit handler of the outer listView where it causes the class cast exception

The last happens because the default commit handler does not consume the event. The solution is to consume it.

Some code (for clarity extracted into a separate class, could just as well be done inline)

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

}
  • Related