JavaFX - Using a ChangeListener on a ComboBox to populate another ComboBox with an ArrayList results in the ArrayList being empty

两盒软妹~` 提交于 2019-12-13 03:07:01

问题


I posted this question two days ago about attaching a ChangeListener to a ComboBox to dictate what output would be shown on a second ComboBox. To put this in short, the idea was that the first ComboBox shows some Unit Types, and depending on the Unit Type chosen would depend on the list of Units shown in the second ComboBox based on the Unit Type chosen. For example If you chose the "Elites" Unit Type in the first ComboBox the second ComboBox should be populated with all the "Elite" units.

Now the person who answered my original question helped a lot with the code in the ChangeListener and I did manage to get it working. However, currrently it only works if you add one single unit at a time to the Unit Type. Ideally I'd want to add an ArrayList or some other suitable data structure of units to the Unit Type, rather than individual units. This would cut down on code, and be more efficent.

So as you can see in this code snippet, currently the elites Unit Type addUnit method only accepts single strings at a time,

elites = new Unit_Type("Elites");
        elites.addUnit("Dreadnought");
        elites.addUnit("Ironclad Dreadnought");

where as I would like it so i could add an ArrayList or simular data structure to the elites Unit Type addUnit method, like this code snippet:

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

elites = new Unit_Type("Elites");
        elites.addUnit(elitesList);

Note that the elitesList is generated by a helper method, as there are 8 other lists like this each representing a different Unit Type. Now i've tried changing code in the Unit Type class, to have the units ArrayList be of type ArrayList<String> and also trying to change the addUnit method to accept a parameter of an ArrayList<String> but whenever i run the program, selecting a Unit Type in the first ComboBox results in an empty array in the second ComboBox.

Here is the rest of the code of both the AddUnitPane class (view), and the Unit_Type class (model)

AddUnitPane Class

public class AddUnitPane extends GridPane
{
    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;

    private ArrayList<String> elitesList, fastAtkList, heavySptList, hqList, lordsowList, specialCList, transportList, troopsList; //Declare the sublists that hold all the units for the type of unit
    Unit_Type elites, fastAttk, heavySpt, hQ, lordsOW, specialC, transport, troops;

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

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

        this.getColumnConstraints().add(col1);
                    //--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

        fastAtkList = createArrayList("Attack Bike", "Stormhawk Interceptor", "Stormtalon Gunship", "Assault", "Bike", "Land Speeder", "Scout Bike");

        heavySptList = createArrayList("Hunter", "Land Raider Crusader", "Land Raider Redeemer", "Land Raider", "Predator", "Stalker", "Stormraaven Gunship", "Vindicator", 
                "Whirlwind", "Centurion Devastator", "Devastator", "Thunderfire Cannon");

        hqList = createArrayList("Captain", "Chaplain", "Librarian", "Techmarine");

        lordsowList = createArrayList("Marneus Calger", "Roboute Guilliman");

        specialCList = createArrayList("Antaro Chronus", "Cato Sicarius", "Ortan Cassius", "Torias Telion", "Varro Tigurius");

        transportList = createArrayList("Drop Pod", "Land Speeder Storm", "Razorback", "Rhino");

        troopsList = createArrayList("Scout", "Tactical");

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

        elites = new Unit_Type("Elites");
        //elites.addUnit(elitesList);
        elites.addUnit("Dreadnought");
        elites.addUnit("Ironclad Dreadnought");

        fastAttk = new Unit_Type("Fast Attack");
        fastAttk.addUnit("Attack Bike");
        fastAttk.addUnit("Stormhawk Interceptor");

        ObservableList<Unit_Type> unitTypeOList = FXCollections.observableArrayList(elites, fastAttk); //add each Unit_Type to an Observable List

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

        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 (first ComboBox)
        unitTypeCombo.setItems(unitTypeOList); //Populate the unitTypeCombo with the UnitTypeOList (observable list) from line 82
        //unitTypeCombo.getSelectionModel().selectFirst(); //Set the unitTypeCombo to show the first item

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

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

        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 void AddUnitHandler(EventHandler<ActionEvent> handler)
    {
        addSquadBtn.setOnAction(handler);       
    }

    private static <T> ArrayList<T> createArrayList(T... items)  //generates the unit lists
    {
        ArrayList<T> result = new ArrayList<>(items.length);
        for (T item : items) 
        {
            result.addAll(result);
        }
        return result;
    }
}

Unit_Type Class

public class Unit_Type implements Serializable 
{
    private String typeName;
    private ArrayList<String> units; //a unit type is an aggregation of units. Tried changing this type to ArrayList<String>

    public Unit_Type(String typeName)
    {
        this.typeName = typeName;
        units = new ArrayList<>();  
    }

    public void addUnit(String u) //tried changing this parameter to ArrayList<String>
    {
        units.add(u);
    }

    public void setTypeName(String name)
    {
        typeName = name; 
    }

    public ObservableList<String> getUnitsForType() //method used in the ChangeListener in the AddUnitPane class
    {   
        ObservableList unitsOList = FXCollections.observableArrayList(units);

        return unitsOList;      
    }

    @Override
    public String toString() //allows the ComboBox to display values correctly
    {
        return typeName;        
    }
}

回答1:


I wouldn't store the backing list in the Unit_Type class. It would be a better idea to store the ObservableList in a field, write a custom serialisation method and make the field transient.

this way you could also initialize the unit types using

elites.getUnitsForType().addAll("Dreadnought", "Ironclad Dreadnought", "Venerable Dreadnought", "Assault Terminator", "Centurion Assault", "Command", "Honour Guard", 
                                "Sternguard Veteran", "Terminator", "Vanguard Veterans");

but it's probably even shorter to initialize the list in the constructor:

public class Unit_Type implements Serializable {

    private String typeName;

    private transient ObservableList<String> units;

    private void writeObject(ObjectOutputStream stream)
            throws IOException {
        stream.defaultWriteObject();

        // serialize units list as string array 
        stream.writeObject(units.toArray());
    }

    private void readObject(ObjectInputStream stream)
            throws IOException, ClassNotFoundException {
        stream.defaultReadObject();

        // read array from stream and initialize list with it
        // Note: because of the way we write objects of this type we can use the raw type here safely
        units = (ObservableList) FXCollections.<Object>observableArrayList((Object[])stream.readObject());
    }

    public Unit_Type(String typeName, String... units) {
        this.typeName = typeName;
        this.units = FXCollections.observableArrayList(units);
    }

    public void setTypeName(String name) {
        typeName = name;
    }

    public ObservableList<String> getUnitsForType() {
        return units;
    }

    @Override
    public String toString() {
        return typeName;
    }
}
elites = new Unit_Type("Elites",
        "Dreadnought", "Ironclad Dreadnought", "Venerable Dreadnought",
        "Assault Terminator", "Centurion Assault", "Command",
        "Honour Guard", "Sternguard Veteran", "Terminator",
        "Vanguard Veterans"
);

During (de)serialisation everything except the ObservableList is handled using the default serialisation mechanism.

stream.defaultWriteObject();
stream.defaultReadObject();

The transient keyword causes the field to be ignored (ObservableLists are not serializable in general, since it would be hard/impossible to persist the listeners especially if they reference UI elements). In this case we simply read/write the strings in the list as array:

stream.writeObject(units.toArray());
units = (ObservableList) FXCollections.<Object>observableArrayList((Object[])stream.readObject());

For more details see the Customize the Default Protocol section here: http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html



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

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