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

Deadly 提交于 2019-12-13 04:18:33

问题


Right, i'm trying to create a program that allows a user to build a Space Marine (Warhammer 40k) army and theres a paticular portion of the program that i'm struggling with. This portion allows the user to select a 'Unit Type' from a ComboBox, then another ComboBox underneath is populated with all of the units for the 'Unit Type' you selected in the first ComboBox.

For example if you selected the Elites 'Unit Type' in the first ComboBox then the second ComboBox should be populated with all the units of the Elites 'Unit Type' which are as follows: "Dreadnought","Ironclad Dreadnought","Venerable Dreadnought", "Assault Terminator", "Centurion Assault", "Command", "Honour Guard", "Sternguard Veteran","Terminator","Vanguard Veterans". There are 8 'Unit Types' each with their own units.

The current setup I have to achieve this is; I have a List of 'Unit Types' called unitTypeList. This List holds all 8 'Unit Types'. I also have 8 other seperate lists (which each represent a 'Unit Type') that hold muliple units for that 'Unit Type' . Then I have one more list that holds each of the 8 seprate lists as mentioned before. So it's a List thats holds 8 other lists.

So to populate the first ComboBox with the 'Unit Types' I placed the unitTypeList in an ObservableList called unitTypeOlist. I then populated the first ComboBox with that ObservableList unitTypeOList. This works fine.

Now in order to populate the second ComboBox with the correct units, i decided to add a ChangeListener to the first ComboBox, so it can take the input of the first ComboBox and then correctly populate the second ComboBox with the units based on the selection of the 'Unit Type' in the first ComboBox.

But this is where the issue occurs. I created another ObservableList in the ChangeListener and place the List that holds the 8 seperate lists, that contain each unit for the 'Unit Type, in it. However when I try to run the program and select a 'Unit Type' from the first ComboBox the programs terminates with a gigantic error starting with; Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: gui.model.Unit_Type cannot be cast to java.lang.Integer.

It's clearly something to do with the casting to an int. But i'm not sure how i would achieve what i want using a ChangeListener any other way. I need some help to meet the requirements in the first paragraph.

I've already tried searching for this, and found this question very helpful already. (My setup is heavily based off this question) But I think my requirements are slighty more complex.

Here is the code for the AddUnitPane class of my program (i've tried to comment it the best i can):

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

回答1:


This is one of the reason why you should avoid using the raw type.

Your ChangeListener is written as if you were listening to a IntegerProperty instead of a Property<Unit_Type>. A cast form a Unit_Type object to Integer is not possible so you get a exception in this expression:

unitList.get((int) arg2)

Your compiler would have complained about this issue, if a ChangeListener<Integer> was used instead.

You could listen to the selectedIndex property instead (checking for valid indices of course - -1 is also a possible value).

It would be easier to simply add the list as property to Unit_Type though. This would allow you to write something like this:

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

BTW: Since you never resize the lists, the following way of initializing them would be much simpler:

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

Even if you want to use a specific list type it would be a good idea to reduce boilerplate code and introduce a helper method:

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");
...


来源:https://stackoverflow.com/questions/46298472/javafx-using-a-changelistener-on-a-combobox-to-populate-a-second-combobox-with

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!