表视图出现javafx - .IndexOutOfBoundsException异常

g6ll5ycj  于 2022-11-20  发布在  Java
关注(0)|答案(1)|浏览(193)

我想问的是,当我尝试从索引为0的supplement的表视图中删除第一行时,为什么会出现IndexOutOfBoundsException。我正在使用一个按钮删除该行
更新:更新代码,使其具有最少的可重现示例
SupplementTest.java

public class SupplementTest extends Application {

    WindowController windowGUI = new WindowController();
    Stage stageGUI;
    Scene sceneGUI;

    @Override
    public void start(Stage primaryStage) throws IOException {

        FXMLLoader assignment2 = new FXMLLoader(getClass().getResource("SupplementFXML.fxml"));
        Parent fxmlFile = assignment2.load();

        try {
            stageGUI = primaryStage;
            windowGUI.initialize();

            sceneGUI = new Scene(fxmlFile, 250, 350);

            stageGUI.setScene(sceneGUI);
            stageGUI.setTitle("Supplement");
            stageGUI.show();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

WindowController.java

public class WindowController {

    Stage newWindow = new Stage();
    boolean deleteSupplement;

    @FXML
    private GridPane primaryGrid = new GridPane();
    @FXML
    private Label supplementLabel = new Label();
    @FXML
    private Button deleteBtn = new Button(), addBtn = new Button();
    public TableView<Supplement> supplementView = new TableView<>();

    int suppIndex;
    ArrayList<Supplement> supplementList = new ArrayList<>();

    // initialize Method
    public void initialize() {
        newWindow.initModality(Modality.APPLICATION_MODAL);
        newWindow.setOnCloseRequest(e -> e.consume());
        initializeWindow();
        updateSupplementList();
    }

    public void initializeWindow() {

        deleteSupplement = false;

        TableColumn<Supplement, String> suppNameColumn = new TableColumn<>("Name");
        suppNameColumn.setCellValueFactory(new PropertyValueFactory<>("supplementName"));
        TableColumn<Supplement, Double> suppCostColumn = new TableColumn<>("Weekly Cost");
        suppCostColumn.setCellValueFactory(new PropertyValueFactory<>("weeklyCost"));
        supplementView.getColumns().addAll(suppNameColumn, suppCostColumn);
        supplementView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        suppIndex = supplementView.getSelectionModel().getSelectedIndex();

        addBtn.setOnAction(e -> {
            supplementList.add(new Supplement("Test1", 10));
            supplementList.add(new Supplement("Test2", 20));
            supplementList.add(new Supplement("Test3", 15));
            updateSupplementList();
        });

        // remove button
        deleteBtn.setOnAction(e -> {
            deleteSupplement = true;
            deleteSupplement();
        });
    }

    public void updateSupplementList() {
        supplementView.getItems().clear();

        if (supplementList.size() > 0) {
            for(int i = 0; i < supplementList.size(); i++) {
                Supplement supplement = new Supplement(supplementList.get(i).getSupplementName(),
                        supplementList.get(i).getWeeklyCost());
                supplementView.getItems().add(supplement);
            }
        }
    }

    public void deleteSupplement() {
        try {
            ObservableList<Supplement> supplementSelected, allSupplement;
            allSupplement = supplementView.getItems();
            supplementSelected = supplementView.getSelectionModel().getSelectedItems();
            supplementSelected.forEach(allSupplement::remove);
            supplementList.remove(suppIndex);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
}

Supplement.java

public class Supplement implements Serializable {

    private String supplementName;
    private double weeklyCost;

    public Supplement() {
        this.supplementName = "";
        this.weeklyCost = 0.00;
    }

    public Supplement(String suppName, double weeklyCost) {
        this.supplementName = suppName;
        this.weeklyCost = weeklyCost;
    }

    public String getSupplementName() {
        return supplementName;
    }

    public double getWeeklyCost() {
        return weeklyCost;
    }

    public void setSupplementName(String supplementName) {
        this.supplementName = supplementName;
    }

    public void setWeeklyCost(double weeklyCost) {
        this.weeklyCost = weeklyCost;
    }

}

如何修复此问题,以便在删除表视图中的任何索引时,不会出现IndexOutOfBoundsException?

eagi6jfj

eagi6jfj1#

很难确定是什么导致了异常,因为您的代码既不完整(因此这里没有人可以复制、粘贴并运行它来重现错误),又非常混乱(其中充满了看起来不必要的代码)。但是:
您似乎在执行两种不同的操作来从表中删除选定的项目:

supplementSelected = supplementView.getSelectionModel().getSelectedItems();
supplementSelected.forEach(allSupplement::remove);

这是一个尝试删除 * 所有 * 选定的项目(虽然我不相信它会工作,如果一个以上的项目被选中)

supplementList.remove(suppIndex);

这将删除选定的项目,如选择模型中的selected index属性所定义。(在单选模型中,它是当前选定的项目;在多选模型中,它是最后选定的项目;如果未选择任何内容,则为-1。)
后者不起作用,因为您只在初始化代码中设置了suppIndex

public void initializeWindow() {

    // ...

    suppIndex = supplementView.getSelectionModel().getSelectedIndex();

    // ...
}

当然,在执行这段代码时,用户还没有机会选择任何内容(此时甚至没有显示表),因此没有选择任何内容,因此suppIndex被赋值为-1

supplementList.remove(suppIndex);

你会得到一个明显的例外。
如果您只支持单选,并且想删除当前选中的项目(或多选中最后选中的项目),只需在当时获取选择即可,您可能还想检查是否选中了某些项目:

public void deleteSupplement() {
    int selectedIndex = supplementView.getSelectionModel().getSelectedIndex();
    if (selectedIndex >= 0) {
        supplementView.getItems().remove(selectedIndex);
    }
}

对此稍有不同,我认为更可取的是使用实际对象而不是其索引:

public void deleteSupplement() {
    Supplement selection = supplementView.getSelectionModel().getSelectedItem();
    if (selection != null) {
        supplementView.getItems().remove(selection);
    }
}

现在,当然(在一个对您的许多代码都通用的主题中),您可以完全删除suppIndex;它是完全多余的。
如果您希望支持多重选择,并删除 * 所有 * 选定项,那么如果选择了多个项,您当前拥有的代码将导致问题。问题是,如果从表的项列表中删除选定项,则也将从选择模型的选定项列表中删除它。因此,当您使用forEach(...)对代码中的选定项列表(supplementSelected)进行迭代时,它会发生变化,这将引发ConcurrentModificationException
若要避免这种情况,您应该将选取项目的清单复制到另一个清单中,然后移除这些项目:

public void deleteSupplement() {
    List<Supplement> selectedItems
        = new ArrayList<>(supplementView.getSelectionModel().getSelectedItems());
    supplementView.getItems().removeAll(selectedItems);
}

当然,这段代码也适用于单个选择(当列表的长度总是0或1时)。
为了解决其他几个问题:保留一个单独的Supplement项列表确实没有意义。表已经保留了该列表,您可以随时使用supplementView.getItems()引用它。(如果您希望在其他地方引用该列表,例如在MVC设计的模型中,您应该确保只有对现有列表的第二个引用;不创建新列表。)
特别是,您不应该在每次向列表中添加新项时完全从头开始重建表。从代码中完全删除冗余的supplementList。完全删除updateSupplementList();首先,它做了太多的工作,其次(也是更重要的),仅仅因为你添加了一个新项目,它就会替换所有现有的项目。2这会丢失重要的信息(例如,它会重置选择)。
要添加新项目,只需

addBtn.setOnAction(e -> {
        supplementView.getItems().add(new Supplement("Test1", 10));
        supplementView.getItems().add(new Supplement("Test2", 20));
        supplementView.getItems().add(new Supplement("Test3", 15));
    });

您的代码中还有许多其他部分没有任何意义,例如:

  • deleteSupplement变量。这似乎没有任何意义。
  • deleteSupplement方法中的try-catch。这里唯一可以抛出的异常是由编程逻辑错误(例如您看到的那个)引起的未检查异常。捕获这些异常没有意义;您需要修复这些错误以便不会抛出异常。
  • @FXML注解。您永远不应该初始化注解为@FXML的字段。这个注解表示FXMLLoader会初始化这些字段。在这个案例中(就我所知),这些字段什至与FXML档案完全没有相关,因此应该移除注解。

相关问题