简体   繁体   English

javafx - 表视图的 IndexOutOfBoundsException

[英]javafx - .IndexOutOfBoundsException for table view

I would to ask why does IndexOutOfBoundsException appear when I try to delete the first row from the table view of supplement which is index 0. I am using a button to delete the row我想问为什么当我尝试从索引 0 的补充表视图中删除第一行时出现 IndexOutOfBoundsException。我正在使用按钮删除该行

Update: update the code to have a minimal reproducible example更新:更新代码以获得最小的可重现示例

SupplementTest.java补充Test.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 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补充.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;
    }

}

How do I fix it so that when I delete any index in the table view the IndexOutOfBoundsException does not appear?如何修复它,以便在删除表视图中的任何索引时不会出现 IndexOutOfBoundsException?

It's difficult to know for certain what is causing the exception, because your code is both incomplete (so no-one here can copy, paste, and run it to reproduce the error), and very confusing (it is full of seemingly-unnecessary code).很难确定是什么导致了异常,因为你的代码既不完整(所以这里没有人可以复制、粘贴和运行它来重现错误),而且非常混乱(它充满了看似不必要的代码). However:然而:

You seem to be doing two different things to delete the selected item(s) from the table:您似乎在做两件不同的事情来从表中删除所选项目:

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

which is an attempt to delete all selected items (though I don't believe it will work if more than one item is selected)这是试图删除所有选定的项目(尽管我不相信如果选择了多个项目它会起作用)

and

supplementList.remove(suppIndex);

which will delete the selected item, as defined by the selected index property in the selection model .这将删除所选项目,如选择 model中的所选索引属性所定义。 (It is the currently selected item in a single selection model, or the last selected item in a multiple selection model, or -1 if nothing is selected.) (单选model为当前选中项,多选model为上次选中项,无选中为-1 。)

The latter will not work, because you only ever set suppIndex in your initialization code:后者将不起作用,因为您只在初始化代码中设置suppIndex

public void initializeWindow() {

    // ...

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

    // ...
}

Of course, when this code is executed, the user has not had a chance to selected anything (the table isn't even displayed at this point), so nothing is selected, and so suppIndex is assigned -1 .当然,当这段代码被执行时,用户还没有机会选择任何东西(此时甚至没有显示表格),所以没有选择任何东西,所以suppIndex被赋值为-1 Since you never change it, it is always -1 , and so when you call因为你永远不会改变它,所以它总是-1 ,所以当你打电话时

supplementList.remove(suppIndex);

you get the obvious exception.你得到明显的例外。

If you are only supporting single selection, and want to delete the currently selected item (or the last selected item in multiple selection), just get the selection at the time.如果只支持单选,想删除当前选中的项(或多选中最后选中的项),获取当时的选中项即可。 You probably still want to check something is selected:您可能仍想检查是否选择了某些内容:

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

A slight variation on this, which I think is preferable, is to work with the actual object instead of its index:对此稍作改动,我认为更可取的是,使用实际的 object 而不是其索引:

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

Now, of course (in a theme that is common to a lot of your code), you can remove suppIndex entirely;现在,当然(在您的许多代码中都有一个主题),您可以完全删除suppIndex it is completely redundant.这是完全多余的。

If you want to support multiple selection, and delete all selected items, then the code you currently have for that will cause an issue if more than one item is selected.如果您想支持多项选择并删除所有选定的项目,那么如果选择了多个项目,您当前拥有的代码将导致问题。 The problem is that if a selected item is removed from the table's items list, it will also be removed from the selection model's selected items list.问题在于,如果从表的项目列表中删除了选定的项目,它也会从选择模型的选定项目列表中删除。 Thus, the selected items list ( supplementSelected ) in your code changes while you are iterating over it with forEach(...) , which will throw a ConcurrentModificationException .因此,当您使用forEach(...)对其进行迭代时,代码中的选定项目列表 ( supplementSelected ) 会发生变化,这将抛出ConcurrentModificationException

To avoid this, you should copy the list of selected items into another list, and remove those items:为避免这种情况,您应该将所选项目的列表复制到另一个列表中,然后删除这些项目:

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

Of course, this code also works with single selection (when the list is always either length 0 or length 1).当然,此代码也适用于单选(当列表始终为长度 0 或长度 1 时)。


To address a couple of other issues: there is really no point in keeping a separate list of Supplement items.要解决其他几个问题:保留单独的Supplement项目列表确实没有意义。 The table already keeps that list, and you can reference it at any time with supplementView.getItems() .该表已保留该列表,您可以随时使用supplementView.getItems()引用它。 (If you wanted to reference the list elsewhere, eg in a model in a MVC design, you should make sure that there is just a second reference to the existing list; don't create a new list.) (如果您想在其他地方引用该列表,例如在 MVC 设计中的 model 中,您应该确保只有对现有列表的第二个引用;不要创建新列表。)

In particular, you should not rebuild the table entirely from scratch every time you add a new item to the list.特别是,您不应在每次向列表中添加新项目时完全从头开始重建表。 Get rid of the redundant supplementList entirely from your code.从您的代码中完全去除多余的supplementList Get rid of updateSupplementList() entirely;完全摆脱updateSupplementList() it is firstly doing way too much work, and secondly (and more importantly) will replace all the existing items just because you add a new one.它首先做了太多工作,其次(更重要的是)将替换所有现有项目,因为您添加了一个新项目。 This will lost important information (for example it will reset the selection).这将丢失重要信息(例如,它将重置选择)。

To add new items, all you need is要添加新项目,您只需要

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

There are various other parts of your code that don't make any sense, such as:您的代码中还有许多其他部分没有任何意义,例如:

  • The deleteSupplement variable. deleteSupplement变量。 This seems to have no purpose.这似乎没有目的。
  • The try - catch in the deleteSupplement method. deleteSupplement方法中的try - catch The only exceptions that can be thrown here are unchecked exceptions caused by programming logic errors (such as the one you see).这里唯一可以抛出的异常是由编程逻辑错误引起的未经检查的异常(比如你看到的那个)。 There is no point in catching those;抓住那些是没有意义的; you need to fix the errors so the exceptions are not thrown.您需要修复错误,以免引发异常。
  • The @FXML annotations. @FXML注释。 You should never initialize fields that are annotated @FXML .您永远不应该初始化@FXML注释的字段。 This annotation means that the FXMLLoader will initialize these fields.此注释意味着FXMLLoader将初始化这些字段。 In this case (as far as I can tell) these are not even associated with an FXML file at all, so the annotation should be removed.在这种情况下(据我所知)这些甚至根本不与 FXML 文件相关联,因此应该删除注释。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM