Az előző részben csináltunk egy térképet, és ott tartottunk, hogy valahogy be kellene kérni, hogy melyik város térképét is kellene megmutatni.
Nos, ha nem GUI-t programoznánk, hanem konzolos alkalmazást csinálnánk, akkor valahogy így gondolkodnánk:
A helyzet az, hogy a konzolos alkalmazásnál a program mondja meg, hogy a felhasználó mit tehet, mikor kell beírni ezt, vagy azt. Ekkor a felhasználónak kell alkalmazkodnia a programhoz, hiszen csak akkor írhatja be a város nevét, amikor a program pont azt kéri.
A GUI-s alkalmazásnál meg a felhasználó mondja meg, hogy mikor mit tesz: tetszőleges sorrendben rákattinthat a dolgokra, beírhat dolgokat, és a programnak kell alkalmazkodnia.
Ez eléggé más nézőpont, nemde? :) Az történik, hogy a képernyőn lévő összes vezérlővel, a gombokkal, a beviteli mezőkkel a felhasználó bármilyen sorrendben bármit csinálhat, és a programnak ezekre az eseményekre kell reagálnia. Ezt úgy nevezik, hogy esemény-orientált (event-driven) programozás. Eseménykezelők azok a kis metódusok, amik elindulnak egy-egy kattintáskor.
Esemény-orientáltan így gondolkodnánk:
A program futása során két dolog tud megváltozni. Ezeket nevezzük a program adatmodelljének:
A programnak ezt kell csinálni:
Szemfülesen észre lehet venni itt olyan mintákat, amik ismétlődnek:
Hú, ez a legelső próba programunk adatokkal, és máris van benne két olyan dolog, amire Dulifuli már figyelmeztetett minket. Hogy is mondta, mi kell ehhez? Azt mondta: data binding. Ez azt jelenti, hogy valahogyan "összekötjük" a program adatmodelljét az ablakban lévő vezérlőkkel:
Fussunk neki újra, ezzel a tudással! A programnak ezt kell csinálni:
Hm, tekerj csak vissza! A konzolos eset sokkal hosszabb volt, a hagyományos data binding nélküli megoldás is elég macerás volt, de ez - ez már tényleg csak két lépés.
Sajnos, amikor a Java-t megalkották, akkor még nem találták fel a data bindinget. Ezért egy ici-pici varázslást kell tenni a dolog köré: minden változóhoz létre kell hoznunk egy konstanst, ami segítségével a data binding a változó értékét tudja piszkálni.
Ez elég ronda, de logikus:
final FieldString<MapWindow> CITY=FieldString.of(MapWindow.class,"city");
Csak emlékezetőnek:
Huhh. Ha ezt egy nekifutásból megértetted, akkor megérdemelsz egy fagyit!
A rövid verzió: ez amolyan "szükséges rossz" dolog. Később egy picit jobban fogunk tudni programozni, és meg fogunk ettől a rondaságtól szabadulni, ne félj!
No, ha eddig jók vagyunk, akkor próbáld ki ezt:
public class MapWindow extends Form {
// a változónk
String city;
// a változóra hivatkozó konstans
final FieldString<MapWindow> CITY=FieldString.of(MapWindow.class,"city");
public MapWindow() {
window()
.text("Térkép")
.add(
edit(this,CITY),view(this,CITY)
)
.show();
}
}
Tehát van benne:
Ha gépelsz valamit a beviteli mezőbe, az változtatja a city változó értékét. Ha a city változó értéke változik a programban, a view annak az értékét kirakja az ablakba. Olyan, mintha ezek össze lennének kötve - ez a data binding, az adat-összekötés!
Valójában nem is gombot szeretnénk, hanem egy Action-t! A felhasználó sokféle módon indíthat műveleteket:
emiatt Dulifuli kiemelte, hogy ezek mind egyforma dolgok: a júzer kattint, és valami történik. Épp csak másként néznek ki az ablakban.
Ezért csináltam egy Action osztályt, ami egy abstract osztály (tehát megígérte, hogy lesz neki egy olyan metódusa, amit ő nem csinált meg, de a leszármazott osztálynak kötelező).
Szóval, ebből származtatunk egy saját példányt, mégpedig anonymous inner class módon. Jajj! Most itt van Anonymous, lehet, hogy még Mátyás királyt is bele fogom keverni? Á, dehogy!
Eddig úgy csináltuk, hogy minden osztály külön file, és így származtatunk saját osztályt:
public class ShowAction extends Action {
public ShowAction() {
// az örökölt konstruktor meghívása
super("Mutasd");
}
@Override
protected void execute() {
// ezt a metódust kell implementálnunk,
// ha majd megnyomja a júzer a gombot,
// ez fog elindulni
}
}
Ez a külön osztályos móka tényleg jó, ha arra használjuk, hogy átláthatóbb legyen tőle a programunk. Azonban ha minden gomb kezelését külön osztályba tesszük, akkor egy igazi programban lesz vagy 150 picike osztályunk, ami nem igazán átlátható.
Ezért találtak ki egy egyszerűsített írásmódot arra, hogy egy osztályból leszármazott osztályt, és abból egy példányt is rögtön tudjunk csinálni - és ez az anonymous inner class. Azért anonymous, mert a leszármazott osztálynak nincs külön neve (a külön osztály példában volt neve, a ShowAction). Azért inner class, mert egy oszályon belül csinálunk egy új osztályt.
Mindenestül így néz ki, ez olyan mint egy jól meghízott változó értékadás, new operátorral, konstruktor hívással (aholis átadjuk az action nevét, hogy Mutasd), meg bajszos zárójelekkel, amik az osztálytól való leszármazásra utalnak:
Action showAction=new Action("Mutasd") {
@Override
protected void execute() {
// ez lesz ha aktiválják az actiont
}
};
Tedd bele ezt a programba! A button() factory method örömmel elfogad egy action típusú változót, és belekukkantva az action nevébe, a megfelelő szöveget rakja ki a gombra.
public class MapWindow extends Form {
String city;
final FieldString<MapWindow> CITY=FieldString.of(MapWindow.class,"city");
public MapWindow() {
window()
.text("Térkép")
.add(
edit(this,CITY),view(this,CITY),button(showAction)
)
.show();
}
Action showAction=new Action("Mutasd") {
@Override
protected void execute() {
System.out.println(city);
}
};
}
Ha megnyomod a gombot, akkor nem csoda, nem ámítás: a System.out.println tényleg kiírja a city értékét, és a city értéke tényleg az, ami éppen a képernyőn szerepel. A data binding csodás!