问题
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 (ObservableList
s 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