Johdetut Observable-arvot
Käyttöliittymässä haluamme usein näyttää varsinaisen tiedon lisäksi tiedoista laskettuja arvoja, kuten arvojen summaa, keskiarvoa, yhdistelmää tai vastaavaa.
Esimerkiksi henkilön koko nimi voidaan laskea etunimen ja sukunimen yhdistelmänä:
String kokonimi = henkilo.getEtunimi() + henkilo.getSukunimi();
Näin laskettu arvo ei kuitenkaan päivity automaattisesti käyttöliittymässä, jos etunimi tai sukunimi muuttuu.
Esimerkki
Katsotaan seuraavaa sovellusta henkilön tietojen syöttämiseksi:
public class MainController implements Initializable {
@FXML
private TableColumn<Pelaaja, String> nimiColumn;
@FXML
private TableColumn<Pelaaja, Number> syntymavuosiColumn;
@FXML
private TableColumn<Pelaaja, Number> ikaColumn;
@FXML
private TableView<Pelaaja> pelaajatTable;
@FXML
private Label pelaajiaLkmLabel;
@FXML
private Button lisaaPelaajaButton;
private ObservableList<Pelaaja> pelaajat = FXCollections.observableArrayList();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
nimiColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nimiColumn.setCellValueFactory(cellData -> cellData.getValue().nimiProperty());
syntymavuosiColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter("####")));
syntymavuosiColumn.setCellValueFactory(cellData -> cellData.getValue().syntymavuosiProperty());
// Tämä ei toimi!
// setCellValueFactory vaatii ObservableValue-arvon, mutta ikä on int-kokonaisluku
// ikaColumn.setCellValueFactory(cellData -> LocalDate.now().getYear() - cellData.getValue().getSyntymavuosi());
lisaaPelaajaButton.setOnAction(event -> {
Pelaaja uusiPelaaja = new Pelaaja();
uusiPelaaja.setNimi("Uusi Pelaaja");
uusiPelaaja.setSyntymavuosi(2000);
pelaajat.add(uusiPelaaja);
});
pelaajatTable.setItems(pelaajat);
}
}
Tässä esimerkissä näemme kaksi ongelmaa:
- Uuden pelaajan lisääminen ei päivitä pelaajien lukumäärää
- Pelaajan syntymävuoden muokkaaminen ei päivitä pelaajan ikää
Observable-arvon muuttaminen toiseksi arvoksi
Kaikki ObservableValue-arvot, kuten StringProperty, IntegerProperty,
FloatProperty, jne. sisältävät map()-apumetodin (ks.
JavaDoc),
jonka avulla arvolle voi suorittaa laskutoimituksia.
Esimerkiksi pelaajan ikä saadaan laskettua syntymävuodesta seuraavasti:
ObservableValue<Number> ika = pelaaja.syntymavuosiProperty().map(vuosi -> LocalDate.now().getYear() - vuosi.intValue());
Tällöin ika-muuttujan sisältämä arvo lasketaan syntymävuodesta kaavalla
LocalDate.now().getYear() - vuosi.intValue() aina, kun pelaajan syntymävuosi
muuttuu. Koska ObservableValue on havaittava arvo, voimme käyttää sitä
setCellValueFactory-metodissa:
public void initialize(URL url, ResourceBundle resourceBundle) {
nimiColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nimiColumn.setCellValueFactory(cellData -> cellData.getValue().nimiProperty());
syntymavuosiColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter("####")));
syntymavuosiColumn.setCellValueFactory(cellData -> cellData.getValue().syntymavuosiProperty());
// HIGHLIGHT_GREEN_BEGIN
ikaColumn.setCellValueFactory(cellData ->
cellData.getValue().syntymavuosiProperty().map(
syntymavuosi -> LocalDate.now().getYear() - syntymavuosi.intValue()));
// HIGHLIGHT_GREEN_END
lisaaPelaajaButton.setOnAction(event -> {
Pelaaja uusiPelaaja = new Pelaaja();
uusiPelaaja.setNimi("Uusi Pelaaja");
uusiPelaaja.setSyntymavuosi(2000);
pelaajat.add(uusiPelaaja);
});
pelaajatTable.setItems(pelaajat);
}
Funktion muuttaminen Observable-arvoksi
Pelaajien lukumäärä saadaan kutsumalla pelaajat.size().
size()-metodi ei kuitenkaan palauta havaittavaa arvoa, eikä pelaajat-lista
sisällä yllä mainittua map()-metodia.
Voimme kuitenkin muuntaa minkä tahansa funktion havaittavaksi käyttämällä
Bindings-luokan (ks.
JavaDoc)
createXBinding-apumetodeja. Tässä X tarkoittaa havaittavan arvon tyyppiä,
eli esimerkiksi Integer, Long, String tai Object.
Koska size() on kokonaisluku, käytämme
Bindings.createIntegerBinding()-metodia (ks.
JavaDoc).
IntegerBinding pelaajienLkm = Bindings.createIntegerBinding(() -> pelaajat.size(), pelaajat);
Bindings.createIntegerBinding() ottaa vähintään kaksi parametria:
lambdalausekkeen, josta havaittava arvo lasketaan ja yhden tai useamman
Observable-arvon, jonka muuttuessa havaittava arvo lasketaan uudestaan.
Tässä tapauksessa ensimmäinen parametri kertoo, että pelaajienLkm-arvo lasketaan aina
lausekkeella pelaajat.size(). Toinen parametri pelaajat kertoo, että arvo on
päivitettävä aina, kun pelaajat-listan sisältö muuttuu.
Bindings.createXBinding-metodi palauttaa Binding-tyyppisen havaittavan
arvon, jonka voi käyttää samalla tavalla kuin muut Observable-arvot.
Tässä tapauksessa voimme sitoa pelaajiaLkmLabel-kentän tekstin
textProperty()-arvoon:
public void initialize(URL url, ResourceBundle resourceBundle) {
nimiColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nimiColumn.setCellValueFactory(cellData -> cellData.getValue().nimiProperty());
syntymavuosiColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter("####")));
syntymavuosiColumn.setCellValueFactory(cellData -> cellData.getValue().syntymavuosiProperty());
ikaColumn.setCellValueFactory(cellData -> cellData.getValue().syntymavuosiProperty().map(syntymavuosi -> LocalDate.now().getYear() - syntymavuosi.intValue()));
lisaaPelaajaButton.setOnAction(event -> {
Pelaaja uusiPelaaja = new Pelaaja();
uusiPelaaja.setNimi("Uusi Pelaaja");
uusiPelaaja.setSyntymavuosi(2000);
pelaajat.add(uusiPelaaja);
});
// HIGHLIGHT_GREEN_BEGIN
IntegerBinding pelaajienLkm = Bindings.createIntegerBinding(() -> pelaajat.size(), pelaajat);
pelaajiaLkmLabel.textProperty().bind(pelaajienLkm.asString());
// HIGHLIGHT_GREEN_END
pelaajatTable.setItems(pelaajat);
}
Property-tyypin bind()-metodilla voimme sitoa arvon toiseen havaittavaan
arvoon. Tässä tapauksessa pelaajiaLkmLabel-kentän teksti sidotaan
pelaajien lukumäärään. Tällöin, jos pelaajat-lista muuttuu, niin
pelaajienLkm-arvo havaitsee muutoksen ja laskee arvonsa uudestaan lausekkeellapelaajat.size()pelaajienLkm.asString()havaitsee muutoksenpelaajienLkm-arvossa ja päivittää arvonsa kutsumallapelaajienLkm.toString()pelaajiaLkmLabel.textProperty()havaitsee muutoksenpelaajienLkm.asString()-arvossa ja päivittää oman sisältönsä vastaamaan uutta arvoa
Muutosten jälkeen pelaajien lukumäärä ja yksittäisen pelaajan ikä päivittyy automaattisesti: