JavaFX - 在 ComboBox 上使用 ChangeListener 以使用多个 ArrayLists 填充第二个 ComboBox

JavaFX - Using a ChangeListener on a ComboBox to populate a second ComboBox with multiple ArrayLists

是的,我正在尝试创建一个程序,允许用户建立一支 Space 海军陆战队(战锤 40k)军队,我正在努力解决该程序的特定部分。这部分允许用户从 ComboBox select 'Unit Type',然后下面的另一个 ComboBox 填充了您在第一个 ComboBox 中 select 编辑的 'Unit Type' 的所有单位.

例如,如果您在第一个 ComboBox 中 selected Elites 'Unit Type',则第二个 ComboBox 应填充 Elites 'Unit Type' 的所有单位,如下所示: "Dreadnought","Ironclad Dreadnought","Venerable Dreadnought","Assault Terminator","Centurion Assault","Command","Honour Guard","Sternguard Veteran","Terminator","Vanguard Veterans"。有 8 'Unit Types' 个,每个都有自己的单位。

我必须实现的当前设置是;我有一个名为 unitTypeList 的 'Unit Types' 列表。此列表包含所有 8 'Unit Types'。我还有 8 个其他单独的列表(每个列表代表一个 'Unit Type'),其中包含 'Unit Type' 的多个单元。然后我还有一个列表,其中包含前面提到的 8 个单独列表中的每一个。所以这是一个包含 8 个其他列表的列表。

因此,为了用 'Unit Types' 填充第一个 ComboBox,我将 unitTypeList 放在名为 unitTypeOlist 的 ObservableList 中。然后我用那个 ObservableList unitTypeOList 填充了第一个 ComboBox。这很好用。

现在为了用正确的单位填充第二个 ComboBox,我决定向第一个 ComboBox 添加一个 ChangeListener,这样它就可以获取第一个 ComboBox 的输入,然后用基于的单位正确地填充第二个 ComboBox在第一个 ComboBox 中 'Unit Type' 的 selection 上。

但这就是问题所在。我在 ChangeListener 中创建了另一个 ObservableList,并放置了包含 8 个单独列表的列表,其中包含“单元类型”的每个单元。但是,当我尝试 运行 程序和 select 来自第一个 ComboBox 的 'Unit Type' 时,程序以一个巨大的错误开始终止;线程 "JavaFX Application Thread" java.lang.ClassCastException 中的异常:gui.model.Unit_Type 无法转换为 java.lang.Integer。

这显然与转换为 int 有关。但我不确定如何使用 ChangeListener 以任何其他方式实现我想要的。我需要一些帮助来满足第一段中的要求。

我已经尝试搜索此内容,发现 this question 已经非常有用了。 (我的设置主要基于这个问题)但我认为我的要求稍微复杂一些。

这是我程序的 AddUnitPane class 的代码(我已尽我所能对其进行评论):

package gui.view;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import elites.terminator.Terminator;
import gui.model.Unit;
import gui.model.Unit_Type;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import troops.scout.Scout;

public class AddUnitPane extends GridPane
{
    private List<Unit_Type> unitTypeList; //Declare the unitTypeList, which holds a list of unit types
    private List<String> elitesList, fastAtkList, heavySptList, hqList, lordsowList, specialCList, transportList, troopsList; //Declare the sublists that hold all the units for the type of unit
    private List<List<String>> unitList; //Declare the unitList that holds all the sublists units
    private Label unitTypeLbl, unitLbl, squadNameLbl, squadSizeLbl;
    private ComboBox<Unit_Type> unitTypeCombo; 
    private ComboBox<String> unitCombo;
    private ComboBox<Integer> squadSizeCombo;
    private TextField squadNameTf;
    private Button addSquadBtn;

    public AddUnitPane()
    {
        this.setVgap(15);
        this.setHgap(20);
        this.setAlignment(Pos.CENTER);

        ColumnConstraints col1 = new ColumnConstraints();
        col1.setHalignment(HPos.RIGHT);

        this.getColumnConstraints().add(col1);

        //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        unitTypeList = new ArrayList<>(); //Initialise the unitTypeList
        Collections.addAll(unitTypeList, new Unit_Type("Elites"), new Unit_Type("Fast Attack"), new Unit_Type("Heavy Support"), new Unit_Type("HQ"), new Unit_Type("Lords Of War"),
                new Unit_Type("Special Characters"), new Unit_Type("Transport"), new Unit_Type("Troops")); //Populate the unitTypeList

        ObservableList unitTypeOList = FXCollections.observableArrayList(unitTypeList); //Add the UnitTypeList to an observableArrayList

        //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        elitesList = new ArrayList<>(); //Initialise and populate all the of the sublists that hold each unit for that type
        Collections.addAll(elitesList, "Dreadnought", "Ironclad Dreadnought", "Venerable Dreadnought", "Assault Terminator", "Centurion Assault", "Command", "Honour Guard", 
                "Sternguard Veteran", "Terminator", "Vanguard Veterans");

        fastAtkList = new ArrayList<>();
        Collections.addAll(fastAtkList, "Attack Bike", "Stormhawk Interceptor", "Stormtalon Gunship", "Assault", "Bike", "Land Speeder", "Scout Bike");

        heavySptList = new ArrayList<>();
        Collections.addAll(heavySptList, "Hunter", "Land Raider Crusader", "Land Raider Redeemer", "Land Raider", "Predator", "Stalker", "Stormraaven Gunship", "Vindicator", 
                "Whirlwind", "Centurion Devastator", "Devastator", "Thunderfire Cannon");

        hqList = new ArrayList<>();
        Collections.addAll(hqList, "Captain", "Chaplain", "Librarian", "Techmarine");

        lordsowList = new ArrayList<>();
        Collections.addAll(lordsowList, "Marneus Calger", "Roboute Guilliman");

        specialCList = new ArrayList<>();
        Collections.addAll(specialCList, "Antaro Chronus", "Cato Sicarius", "Ortan Cassius", "Torias Telion", "Varro Tigurius");

        transportList = new ArrayList<>();
        Collections.addAll(transportList, "Drop Pod", "Land Speeder Storm", "Razorback", "Rhino");

        troopsList = new ArrayList<>();
        Collections.addAll(troopsList, "Scout", "Tactical");

        //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        unitList = new ArrayList<>(); //Initialise and populate the List thats holds other Lists that hold the different units 
        Collections.addAll(unitList, elitesList, fastAtkList, heavySptList, hqList, lordsowList, specialCList, transportList, troopsList);

        //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


        unitTypeLbl = new Label("Select The Unit Class: "); //Initialise all of the labels
        unitLbl = new Label("Select The Unit: ");
        squadNameLbl = new Label("Squad Name: ");
        squadSizeLbl = new Label("Squad Size");

        //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        unitTypeCombo = new ComboBox<Unit_Type>(); //Initialise the unitTypeCombo
        unitTypeCombo.setItems(unitTypeOList); //Populate the unitTypeCombo with the UnitTypeOList (observable list) from line 56
        unitTypeCombo.getSelectionModel().selectFirst(); //Set the unitTypeCombo to show the first item

        unitCombo = new ComboBox<>(); //Initialise the unitCombo

        unitTypeCombo.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() //add a change listener to the unitTypeCombo
        {
            @Override
            public void changed(ObservableValue arg0, Object arg1, Object arg2) 
            {
                ObservableList unitOList = FXCollections.observableArrayList(unitList.get((int) arg2)); //add the unitList from line 88 to an observable list and cast arg2 to an int
                unitCombo.setItems(unitOList); //set the unitCombo items with the observable list from above

            }           
        });

        unitCombo = new ComboBox<>();

        squadNameTf = new TextField();

        squadSizeCombo = new ComboBox<>();

        addSquadBtn = new Button("Add Squad");

        this.add(unitTypeLbl, 0, 1);
        this.add(unitTypeCombo, 1, 1);

        this.add(unitLbl, 0, 2);
        this.add(unitCombo, 1, 2);

        this.add(squadNameLbl, 0, 3);
        this.add(squadNameTf, 1, 3);

        this.add(squadSizeLbl, 0, 4);
        this.add(squadSizeCombo, 1, 4);

        this.add(new HBox(), 0, 5);
        this.add(addSquadBtn, 1, 5);        
    }

    public ComboBox<Unit_Type> getUnitClass()
    {
        return unitTypeCombo;       
    }

    public ComboBox<String> getUnit()
    {
        return unitCombo;
    }

    public String getSquadName()
    {
        return squadNameTf.getText();
    }

    public ComboBox<Integer> getSquadSize()
    {
        return squadSizeCombo;
    }

    public void AddUnitHandler(EventHandler<ActionEvent> handler)
    {
        addSquadBtn.setOnAction(handler);       
    }
}

这是您应避免使用原始类型的原因之一。

你的 ChangeListener 就像你在听 IntegerProperty 而不是 Property<Unit_Type> 一样。从 Unit_Type 对象到 Integer 的强制转换是不可能的,所以你在这个表达式中得到一个异常:

unitList.get((int) arg2)

如果使用 ChangeListener<Integer>,您的编译器会抱怨这个问题。

您可以改为收听 selectedIndex 属性(当然要检查有效索引 - -1 也是一个可能的值)。

不过,简单地将列表添加为 属性 到 Unit_Type 会更容易。这将允许你写这样的东西:

unitTypeCombo.valueProperty().addListener(new ChangeListener<Unit_Type>() {
    @Override
    public void changed(ObservableValue<? extends Unit_Type> observable, Unit_Type oldValue, Unit_Type newValue) {
        unitCombo.setItems(newValue == null ? FXCollections.emptyObservableList() : newValue.getUnits());
    }
});

顺便说一句:由于您从不调整列表的大小,因此以下初始化列表的方法会简单得多:

elitesList = Arrays.asList("Dreadnought", "Ironclad Dreadnought", "Venerable Dreadnought", "Assault Terminator", "Centurion Assault", "Command", "Honour Guard", 
                           "Sternguard Veteran", "Terminator", "Vanguard Veterans");
...

即使您想使用特定的列表类型,减少样板代码并引入辅助方法也是一个好主意:

private static <T> List<T> createArrayList(T... items) {
    List<T> result = new ArrayList<>(items.length);
    for (T item : items) {
        result.add(result);
    }
    return result;
}
elitesList = createArrayList("Dreadnought", "Ironclad Dreadnought", "Venerable Dreadnought", "Assault Terminator", "Centurion Assault", "Command", "Honour Guard", 
                             "Sternguard Veteran", "Terminator", "Vanguard Veterans");
...