6. gyakorlat¶
Contact alkalmazás folytatása¶
Az előző óra végén a listából a kontaktok törlését megvalósítottuk.
Mielőtt a módosítást megcsinálnánk elkészítjük a fő ablak menüjében a Close menüpont eseménykezelését.
1 2 3 4 5 6 7 8 | |
Az FXML-ben a következőnek kell szerepelnie, melyet a SceneBuilder-rel is megadhatunk (Code -> onAction):
1 | |
Ezzel meg is vagyunk, így nézzük az editContact metódust!
Mivel a szerkesztéshez egy új ablakra lesz szükségünk, ahol magát a kontaktot szerkeszteni tudjuk, így létre is fogunk hozni egyet /fxml/add_edit_contact.fxml néven a resources mappa alatt és ezt az FXML állományt fogjuk betölteni.
Ezt a formot majd a hozzáadásnál is használhatjuk, így ilyen szemlélettel alakítjuk ki a felületet, illetve a neve is ezt tükrözi.
1 2 3 4 5 6 7 8 9 10 11 12 | |
Mivel a form-on szeretnénk majd megjeleníteni a szerkeszteni kívánt kontakt információit, így ezt át fogjuk adni a controller számára. Hozzuk is létre az FXML állományt és a hozzá tartozó AddEditContactController-t! Készítsük el a felületet mely a következőképpen néz ki a SceneBuilder-ben: A bal oldalon látható TreeView segítséget nyújt a pontos elrendezés kialakításában

Az elemek pontos tulajdonságánál segítséget nyújt az FXML kód, melyből láthatjuk a megadott fx:id-kat és az eseménykezelőket is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
Ezen a ponton ellenőrizzük, hogy az edit contact gomb megnyomása esetén sikerül-e betölteni az FXML állományt, de ehhez először szükségünk van a setContact metódus definíciójára.
1 2 3 4 5 6 7 8 | |
Fejlesszük tovább az AddEditContactController-t úgy, hogy az FXML-ben megadott vezérlőket injektáljuk a controller-be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Ezután a felületi vezérlők értékét (a fent injektált elemek) be szeretném állítani a megkapott contact megfelelő property értékeire.
Így itt kötéseket fogok alkalmazni, melyet akkor hozok létre, amikor a setContact-ot meghívja valaki (azaz új kontakt kerül a felülethez).
A setContact ennek megfelelően a következőképpen néz ki:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Mivel a contact alapvetően nem tud a hozzá tartozó phone-okról, így ezt a phoneDAO-tól kérjük le, melyet field-ként adjunk is meg a controller-ben:
1 | |
A ListView minket érdeklő property-je az itemsProperty, mely a benne lévő elemeket adja meg ObjectProperty <ObservableList <T>> formájában, ahol a T típusparaméter jelen esetben nyilván Phone.
A Contact bean-ben nem véletlenül ilyen módon adjuk meg a telefonokat (ObjectProperty<ObservableList<Phone>>).
A dateOfBirth vezérlő egy valueProperty-vel rendelkezik, mely visszaad egy ObjectProperty<T> property-t, ahol T jelenleg LocalDate, melyet szintén ilyen módon adtunk meg a Contact osztályban.
A kétirányú kötés inicializálása fontos, mégpedig olyan szempontból, hogy az értékeket szinkronba fogjuk hozni és így az egyik property elveszti a jelenlegi értékét.
Ha vesszük például a name property kötését, akkor a name felületi vezérlő értékét kötjük hozzá a contact nameProperty-jéhez, azaz a contact értéke másolódik a name felületi vezérlő property-jébe.
Fordított esetben, azaz: contact.nameProperty().bindBidirectional(name.textProperty()); esetében a contact objektumunk elvesztette volna az aktuális értékét és a felületen sem jelenne meg semmilyen érték.
Ezen a ponton ellenőrizzük, hogy a megadott elemek megjelennek-e a felületen (Az adatbázisban adjunk meg ).
A telefonok listájában, azaz a ListView<Phone> elemben az egy cellában megjelenő elemekre alapból a toString()-et hívja a rendszer és ezt írja ki a cellába, ezért láthatunk rosszul megjelenő (Object osztály toString() alapú) adatokat.
Nincs más teendő, mint a Phone osztály toString metódusát elkészíteni:
1 2 3 4 5 6 7 8 | |
Ha ezekkel megvagyunk akkor jöhet a tényleges mentés, de előtte implementáljuk a Cancel gomb eseménykezelését, mely szimplán annyit tesz, hogy visszatölti a fő ablakot.
1 2 3 4 | |
A mentéshez szükségünk lesz egy ContactDAO-ra, így a következő field megadást is használjuk:
1 | |
Maga a mentés a következőképpen néz ki:
1 2 3 4 5 6 7 8 9 10 | |
Elsőre furán nézhet ki a telefonszámok törlése, majd létrehozása. Ezt azért csináljuk ilyen módon, mert majd a telefonszámokat is módosítani szeretnénk, ugyanakkor azokat egyből nem szeretnénk menteni, csak akkor amikor a kontakt szerkesztés/hozzáadás felületen megnyomjuk a Save gombot. Mivel kicsit nehezebb így számontartani, hogy mi változott, a legegyszerűbb az, ha minden számot kitörlünk és újra hozzáadjuk őket (lehet, hogy nem is változott semmi, de ettől még működni fog a fenti kódrészlet csak kis plusz melót végez).
A deleteAll phoneDAO metódus még nem létezik, de azt nagyon könnyen implementálhatjuk (nyilván adjuk hozzá az interface-hez is a metódus fejlécét):
1 2 3 4 | |
Miután elmentettük a contact-ot és a telefonszámokat, ebben az esetben is visszatérünk a fő ablakhoz.
A telefonok listájánál végigvihetjük a kontakt táblázatban látott megvalósítást is, amikor az Edit és a Delete gombokat hozzáadtuk egy külön oszlopban.
A ListView-nál ez annyiban különbözne, hogy valamilyen Label-t is hozzáadnánk, melyen magát a telefonszámot adjuk meg, továbbá a két gombot is.
Ez azért kell mert a ListView csak elemek sora, azaz olyan mintha egy egy oszlopos TableView-nk lenne.
Annak érdekében, hogy egy újabb lehetőséget ismerjünk meg, így egy ContextMenu fogunk készíteni (jobb klikk az adott ListView elemen), melyben az Edit és a Delete menüpontokat fogjuk elhelyezni.
Ehhez szükségünk lesz az initialize metódusra, így a controllert implementálja az Initializable interface-t!
Maga az initialize a következőképpen alakul:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
A setCellFactory(Callback <ListView<T>,ListCell<T>> value)-ban megadott CAllback egy ListCell<T> ad vissza.
Ezt a cellát létre is hozzuk és később fel is használjuk, előtte azonban létrehozzuk a ContextMenu-t és hozzáadjuk a két MenuItem objektumot, amelyeket szintén itt hoztunk létre.
A gombok viselkedését később nézzük meg, most elég a többi részt értelmeznünk, ahhoz, hogy a context menüt meg tudjuk jeleníteni.
Mivel egy ListView tartalmazhat több sort, mint amennyi tényleges Phone van benne így kezelnünk kell azt az esetet, ha az adott sor üres.
A cella szövegét ettől tesszük függővé a new When(cell.itemProperty().isNotNull()).then(cell.itemProperty().asString()).otherwise(""); kötés létrehozásával, mely kötés aktuális értékét hozzákötjük a cella textProperty-kéhez.
Ezen felül csinálunk egy ChangeListener-t is, mely azt figyeli, hogy ha az adott cella értéke üres lesz akkor nem ad hozzá context menüt (cell.setContextMenu(null);), illetve ha van érték a sorban, akkor hozzáadja a context menüt.
Végül visszaadjuk magát a cell-t.
Ezután nézzük az eseménykezelőket!
A legfontosabb, hogy tudnunk kell, hogy melyik soron kattintottunk a kontextus menü elemekre (melyik Phone objektumot szeretnénk módosítani vagy törölni).
Ehhez a cell.getItem() metódus ad segítséget, mely visszaadja a cellához rendelt Phone objektumot.
Kezdjük a törléssel, mert az egyszerűbb:
1 2 3 | |
Mivel a contact mentésénél törlünk minden az adott contact-hoz rendelt Phone-t, majd utána a következőképpen mentjük a telefonszámokat: contact.getPhones().forEach(...), így elegendő, ha a contact.getPhones() listából kitöröljük azt a Phone-t, amelynek cellájában a context menü törlést kiválasztottuk.
Fontos látni, hogy ezen a ponton az adatbázisban semmit sem matattunk.
A módosításhoz szükség lesz egy form-ra, melyet egy modális ablakban (nem lehet belőle kikattintani) szeretnénk megjeleníteni.
Ezért itt hívjuk meg a showPhoneDialog metódust az adott Phone típusú paraméterrel!
1 2 3 4 | |
Maga a showPhoneDialog a következőképpen néz ki:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
A kód nagy része megegyezik azzal amit az App.loadFXML-en belül megcsináltunk.
Itt egy saját Stage-be szeretném belerakni a tartalmat, így a fenti megközelítést alkalmazom.
Ami fontos, hogy a kontrolleren beállítjuk a stage, phone és contact objektumokat, mivel ezekre szükség lesz (később látjuk).
Challenge
Oldjuk meg úgy, hogy az eddigi hívásokat ne kelljen átírnunk, viszont legyen lehetőség arra is, hogy a fenti problémát kiküszöböljük.
Az egyik probléma az, hogy van olyan eset, amikor műveleteket is szeretnénk elvégezni a controller-en, a másik egyszerűbb probléma, hogy mi legyen a Stage-el.
Megoldás
Az App.java a következőképpen változik:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Ilyen módon sehol nem kell változtatnunk a meglévő loadFXML hívásokon, azonban a showPhoneDialog-on belül tudjuk a következőt csinálni:
1 2 3 4 5 6 | |
Kössük be az Add gombhoz is a showPhoneDialog-ot (onAction legyen beállítva a gombon):
1 2 3 4 5 6 7 8 9 10 | |
Ezután hozzuk létre az /fxml/add_edit_phone.fxml állományt, illetve a hozzá tartozó AddEditPhoneController-t.
Hozzuk is létre a controller-ben az init metódust:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Miután ezzel megvagyunk, alakítsuk ki a következő felületet a SceneBuilder segítségével (itt is segít a bal oldali tree view):

Az FXML-hez tartozó, generált FXML kód a követkető:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
Az egyetlen új vezérlő a ComboBox, melyet a PhoneType-nál alkalmazunk.
Ezen a ponton ellenőrizhetjük, hogy megfelelően betölti-e az oldalt az alkalmazás (mind az Add és mind az Edit esetén).
Ezután a controllerbe injektáljuk a szükséges grafikus vezérlő elemeket:
1 2 3 4 5 | |
A ComboBox választható elemeinek a megadásához használjuk az Initializable interface initialize metódusát:
1 | |
Itt lekérjük az összes lehetséges enum értéket és ezt állítjuk be lenyíló doboznak.
A korábban megírt init metódusban ezután a kapott Phone tulajdonságok alapján hozzuk létre a felületi kötéseket, hasonlóan mint ahogy azt a Contact esetében is megtettük:
1 2 3 4 5 6 7 8 9 10 11 12 | |
A kötések létrehozása után megvizsgáljuk, hogy az adott Phone-hoz tartozik-e phoneType (kötés miatt ezt látjuk már), amennyiben nem akkor egy új telefonról van szó és ilyen esetben az első lehetséges opciót választjuk ki az enum értékei közül.
A felületen elhelyezett Cancel gomb hatására az init-ben beállított stage-et fogom bezárni:
1 2 3 4 | |
A Save egy kicsit fura felépítésű lesz:
1 2 3 4 5 6 | |
A törlés és utána hozzáadás ugyanarra az objektumra redundánsnak tűnik, de mivel ez felelős a hozzáadás és a módosításért is, így van létjogosultsága (új elem esetén nyilván nem tudja kitörölni, csak hozzáadni).
Ellenőrzések és kiegészítések¶
Az első egy apró kiegészítés a kontakt törléshez, mivel a kontakthoz rendelt telefonszámokat nem töröltük ki. A megerősítés után ezt megtehetjük a következőképpen:
1 2 3 4 5 6 7 8 9 | |
Contact ellenőrzések¶
Ezután végezzünk el a kötelező elemek megadásának ellenőrzését az AddEditContactController-ben:
Először is le szeretnénk tiltani a gombot (nem lehet megnyomni), addig amíg van érvénytelen adat.
Helyezzük el a két Label-t a felületen, ahova a hibaüzeneteket fogjuk írni, illetve injektáljuk is ezeket a controllerbe:
1 2 3 4 5 6 7 8 | |
Az initialize metódust ezután a következőképpen módosítjuk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Így, a gomb egészen addig disabled állapotban lesz, ameddig vagy a name vagy az email vagy a születési dátum üres/null.
Az egyes hibajelző Label-ek értékét ChangeListener-ek alapján állítjuk be.
Keresés a kontaktok listájában¶
Először alakítsuk ki a felületi vezérlőket a main_window.fxml oldalon.
A BorderPane bal oldali eleméhez adjuk hozzá a következőket:
1 2 3 4 5 6 7 8 9 10 11 12 | |
A MainWindowController-en belül injektáljuk a két TextField objektumot, majd készítsük el az onSearch metódust.
Jelen esetben nem adatázisműveletként valósítjuk meg a keresést (bár valós környezetben így volna szép), hanem a lekért teljes listát filterezzük.
Emiatt a refreshTable-t módosítjuk úgy, hogy a kontaktok listáját egy field-ben áltároljuk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Az onSearch-ön belül Stream API-t használunk az elemek szűréséhez, majd ezt állítjuk be a táblázat elemeiként.
Videók¶
Gyakorló Projekt - Pizzázás¶
A feladat egy pizzanyilvántartó program elkészítése Java nyelven, amely követi az MVC modellt. A programnak a következő funkcionalitásokat kell támogatnia.
Pizza felvétele¶
A programnak támogatnia kell a pizza felvételét, ahol a következő adatokat kell felvennie:
- Pizza neve, ami szövegesen adható meg
- Pizza leírása, ami tartalmazza, hogy milyen összetevői vannak a pizzának
- Vegetáriánus jelző (Checkbox)
- És végül az ára, ami egy egész szám (Spinner, Slider)
Megszorítások, követelmények:
- A pizza beszúrásakor kapjon egy egyedi azonosítót.
- A sikeres beszúráskor egy ablak jelenjen meg, amely megmondja a beszúrt pizza sorszámát.
- A rendszer nem tárolhat két ugyanolyan nevű pizzát. Ha ugyanolyan nevű pizzát akarunk beszúrni, akkor a rendszer adjon hibaüzenetet (de ne zárja be a beviteli ablakot).


Pizzák listázása¶
A programnak támogatnia kell a pizzák listázását. Nem kell, hogy automatikusan történjen, elegendő valamilyen funkcionalitást biztosítani erre.

Pizza rendelés¶
A programnak támogatni kell a pizza rendelést is. Ez azt jelenti, hogy a felhasználó kiválaszthatja, hogy milyen pizzát szeretne rendelni, mekkora méretűt, hány darabot, és akkor megrendelheti.
- A pizzát kiválasztani a legördülő menü segítségével lehet, ahol az összes elérhető pizza közül lehet választani.
- A kiválasztott pizza tulajdonságai jelenjen meg alatta.
- A kicsi pizza 20%-kal olcsóbb, mint a normál méretű, míg a nagy 20%-kal drágább.

Típushibák¶
Fordítási hibák¶
- Nem implementált örökölt metódus
- Absztrakt osztály példányosítása
- FXML
- Rossz osztály van megadva
- @FXML annotáció hiánya
- Rossz az FXML fájl útvonala
- Rossz import:
javafx.valamihelyett példáuljava.awt.valami - Örökölt metódus láthatósága csökken
Futási hibák¶
Adatbázis, DAO¶
- Rossz az SQL parancs, ami létrehozza a kapcsolatot az adatbázis felé
getConnection("jdbc:sqlite:" + DBFILE);helyettgetConnection("jdbc:sqlite" + DBFILE);getConnection("jdbc:sqlite: + DBFILE");getConnection("jdbc.sqlite:" + DBFILE);
INSERT INTO Pizza (Nev, Leiras, Vega, Ar) VALUES (?, ?, ?, ?)hibás- Hibás táblanév, mezővés
VALUEShelyettVALUE
PreparedStatementesetében asetXXXesetében az indexelésnek 1-től kell indulnia, és nem 0-tól- A DAO-ban Adatbázis fájl rosszul van megadva
View¶
- Rosszul összerakott GUI
- Egy elem kétszer is előfordul a fában
Logikai hibák¶
Olyan hibák, amelyek nem vezetnek futási hibához, de nem jól működik a program
- A DAO vagy Controller esetében az interfésznek kell lennie a statikus típusnak, és a konkrét megvalósítás a dinamikus típus
- DAO-ban a list esetében nem töröljük a listát, mielőtt ismét feltöltenénk
ConstructTabletöbbszöri meghívása (plusz oszlopok lesznek minden hívásnál)- Rossz képletek, inicializálatlan értékek, objektumok