0

I'm trying to create a TableView which contains Student objects and Classroom objects. Now, I want to have a ChoiceBox to swap Students from one Classroom to another.

Here's the code

public class ChoiceBoxCell extends TableCell<Student, Classroom> {


    ChoiceBox<Classroom> classroomChoiceBox = new ChoiceBox<>();

    public ChoiceBoxCell(ObservableList<Classroom> classroomObservableList) {
        ObservableList<Classroom> classroomObservableListList = classroomObservableList;
        classroomChoiceBox.setItems(classroomObservableListList);

        classroomChoiceBox.getSelectionModel().selectedIndexProperty().addListener((obs, oldValue, newValue) -> {
            Classroom value = classroomChoiceBox.getItems().get((int) newValue);
            classroomChoiceBox.setValue(value);
            processEdit(value);
        });
    }
    private void processEdit(Classroom value) {
        commitEdit(value);
        classroomChoiceBox.setValue(value);
        setGraphic(classroomChoiceBox);
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setGraphic(classroomChoiceBox);
    }

    @Override
    public void commitEdit(Classroom value) {
        super.commitEdit(value);
        classroomChoiceBox.setValue(value);
        setGraphic(classroomChoiceBox);
    }

    @Override
    public void startEdit() {
        super.startEdit();
        Classroom value = getItem();
        if (value != null) {
            classroomChoiceBox.setValue(value);
            setGraphic(classroomChoiceBox);
        }
    }

    @Override
    protected void updateItem(Classroom item, boolean empty) {
        super.updateItem(item, empty);
        if (item == null || empty) {
            classroomChoiceBox.setValue(item);
            setGraphic(classroomChoiceBox);
        } else {
            classroomChoiceBox.setValue(item);
            setGraphic(classroomChoiceBox);
        }
    }
}

In my TableView class

List classroomList = new ClassroomDao().getAllClasses(); ObservableList classroomObservableList = FXCollections.observableArrayList(classroomList);

classroomNameColumn.setPrefWidth(columnSize);
classroomNameColumn.setCellValueFactory(cdf -> cdf.getValue().classroomProperty());
classroomNameColumn.setCellFactory(column -> new ChoiceBoxCell(classroomObservableList));
classroomNameColumn.setEditable(true);

I'm getting the following exception

Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.elementData(ArrayList.java:418)
at java.util.ArrayList.get(ArrayList.java:431)
at com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89)
at view.adminAccess.studentOverview.ChoiceBoxCell.lambda$new$0(ChoiceBoxCell.java:20)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ReadOnlyIntegerWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:176)
at javafx.beans.property.ReadOnlyIntegerWrapper.fireValueChangedEvent(ReadOnlyIntegerWrapper.java:142)
at javafx.beans.property.IntegerPropertyBase.markInvalid(IntegerPropertyBase.java:113)
at javafx.beans.property.IntegerPropertyBase.set(IntegerPropertyBase.java:147)
at javafx.scene.control.SelectionModel.setSelectedIndex(SelectionModel.java:68)
at javafx.scene.control.SingleSelectionModel.select(SingleSelectionModel.java:114)
at javafx.scene.control.ChoiceBox$4.invalidated(ChoiceBox.java:331)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:111)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.ChoiceBox.setValue(ChoiceBox.java:336)
at view.adminAccess.studentOverview.ChoiceBoxCell.updateItem(ChoiceBoxCell.java:58)
at view.adminAccess.studentOverview.ChoiceBoxCell.updateItem(ChoiceBoxCell.java:10)
at javafx.scene.control.TableCell.updateItem(TableCell.java:639)
at javafx.scene.control.TableCell.indexChanged(TableCell.java:468)
at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
at com.sun.javafx.scene.control.skin.TableRowSkinBase.requestCellUpdate(TableRowSkinBase.java:659)
at com.sun.javafx.scene.control.skin.TableRowSkinBase.lambda$init$497(TableRowSkinBase.java:159)
at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:137)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:105)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.Cell.setItem(Cell.java:403)
at javafx.scene.control.Cell.updateItem(Cell.java:670)
at javafx.scene.control.TableRow.updateItem(TableRow.java:268)
at javafx.scene.control.TableRow.indexChanged(TableRow.java:225)
at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
at com.sun.javafx.scene.control.skin.VirtualFlow.releaseCell(VirtualFlow.java:1807)
at com.sun.javafx.scene.control.skin.VirtualFlow.getCellLength(VirtualFlow.java:1881)
at com.sun.javafx.scene.control.skin.VirtualFlow.computeViewportOffset(VirtualFlow.java:2528)
at com.sun.javafx.scene.control.skin.VirtualFlow.layoutChildren(VirtualFlow.java:1189)
at javafx.scene.Parent.layout(Parent.java:1079)
at javafx.scene.Parent.layout(Parent.java:1085)
at javafx.scene.Parent.layout(Parent.java:1085)
at javafx.scene.Parent.layout(Parent.java:1085)
at javafx.scene.Parent.layout(Parent.java:1085)
at javafx.scene.Scene.doLayoutPass(Scene.java:552)
at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
at com.sun.javafx.tk.Toolkit.lambda$runPulse$31(Toolkit.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.PaintCollector.liveRepaintRenderJob(PaintCollector.java:320)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$ViewEventNotification.run(GlassViewEventHandler.java:788)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$ViewEventNotification.run(GlassViewEventHandler.java:749)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleViewEvent$369(GlassViewEventHandler.java:828)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleViewEvent(GlassViewEventHandler.java:827)
at com.sun.glass.ui.View.handleViewEvent(View.java:539)
at com.sun.glass.ui.View.notifyResize(View.java:875)
at com.sun.glass.ui.win.WinWindow._setBounds(Native Method)
at com.sun.glass.ui.Window.setBounds(Window.java:572)
at com.sun.javafx.tk.quantum.WindowStage.setBounds(WindowStage.java:318)
at javafx.stage.Window$TKBoundsConfigurator.apply(Window.java:1274)
at javafx.stage.Window$TKBoundsConfigurator.pulse(Window.java:1290)
at com.sun.javafx.tk.Toolkit.lambda$runPulse$31(Toolkit.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:378)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$405(QuantumToolkit.java:319)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)

This is how it looks like. It just seems like the last cell is trying to fit every single row in the table with a ChoiceBox.

How can I keep this limited only to the rows which have data in them?

EDIT: additional issue

Part of the code I'm using now.

classroomChoiceBox.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
        if (newValue != null) {
            processEdit(newValue);
        }
    });

@Override
public void commitEdit(Classroom value) { // always gets executed
    super.commitEdit(value);
    Student student = (Student) getTableRow().getItem();
    student.setClassroom(value);
    new StudentDao().updateStudent(student); // students get updated on creation of the table
    classroomChoiceBox.setValue(value);
    setGraphic(classroomChoiceBox);
}

The issue is that each Student gets updated when the table is created.

1 Answers1

0

The selected index in a selection model is set to -1 if nothing is selected (see docs). Since -1 is not a valid index in a list, you need to check for this case:

classroomChoiceBox.getSelectionModel().selectedIndexProperty().addListener((obs, oldValue, newValue) -> {
    int index = newValue.intValue();
    if (index >= 0) {
        Classroom value = classroomChoiceBox.getItems().get(index);

        // what is the point of the next line? 
        // surely this is the value in the choice box already???
        classroomChoiceBox.setValue(value);

        processEdit(value);
    }
});

Of course, it may be easier just to get the value directly from the selection model, instead of getting its index:

classroomChoiceBox.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
    if (newValue != null) { /* don't know if you care if this is null... */
        processEdit(newValue);
    }
});

The reason you are seeing choice boxes in the empty cells is that you explicitly set the graphic to the choice box in empty cells, in the cell's updateItem method. I assume (since the if block is identical to the else block) that this is just a copy-and-paste error of some kind.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Great, thanks a lot. The first solution works perfectly (The 2nd one doesn't as the newValue is a `Number` and I need a `Classroom` object. Anyways, it doesn't matter as the first one works without any problems). – Honza Štefánik Jan 11 '17 at 14:41
  • @HonzaŠtefánik apparently you didn't read the answer properly. The `newValue` is a `Classroom`, not a `Number` if you change it from `selectedIndexProperty` to `selectedItemProperty`. – James_D Jan 11 '17 at 14:42
  • Oh, it turns out I don't even need the `if (index >= 0)`. It was probably the wrong `setGraphic` then. And yes, I don't need the ` classroomChoiceBox.setValue(value);` either. Thanks once more. – Honza Štefánik Jan 11 '17 at 14:44
  • You probably don't need it if you're not displaying the combo box in cells with a null item. – James_D Jan 11 '17 at 14:45
  • My bad, I didn't even notice that, sorry. – Honza Štefánik Jan 11 '17 at 14:45
  • May I ask one more question? I want to use this `ChoiceBox` to switch `Students` to different `Classrooms`. Now, I have found an issue (which makes the program slower, it works though) that the code in `commitEdit()` function gets executed when I create the table, causing the `Students` to get updated when creating the table. Any idea how I can prevent this? I'll edit the issue to my original question. – Honza Štefánik Jan 11 '17 at 20:38
  • @HonzaŠtefánik If you have new questions, ask new questions. Don't continually update existing ones. – James_D Jan 11 '17 at 20:39
  • Alright, sorry. [Here](http://stackoverflow.com/questions/41600356/commitedit-function-gets-executed-on-creation-of-the-table) is the new question. – Honza Štefánik Jan 11 '17 at 20:44