Na konci minulého dílu jsme si jednak rozebrali základní předpoklady pro možnosti obecnějšího řešení dané úlohy a také si připravili vše potřebné pro implementaci nějakého konkrétního kódu. Proto nebudeme dlouho otálet a ukážeme si kód procedury pro čtení dat a jejich uložení do lokální proměnné:
private ObservableList<ObservableList> dataView (final String query, final Integer[] cols){ --1 ObservableList<ObservableList> data = FXCollections.observableArrayList(); --2 PreparedStatement pStat = null; --3 String item = null; --4 Integer type = null; --5 Double dbl = null; --6 CONN = connDB("fxguide", "fxguide"); --7 try { pStat = CONN.prepareStatement(query); --8 ResultSet rs = pStat.executeQuery(); --9 while (rs.next()) { --10 ObservableList<Object> row = FXCollections.observableArrayList(); --11 for (int i=1;i<=cols.length;i++){ --12 item=rs.getObject(i).toString(); --13 type=cols[i-1]; --14 switch (type) { case 0: row.add(item); break; --15 case 1: row.add(item.replaceAll("\\s+$", "")); break; --16 case 2: dbl=Double.valueOf(item); row.add(nform2.format(dbl)); break; --17 case 3: row.add(item.substring(8) + "." + item.substring(5, 7) + "." + item.substring(0, 4)); break; --18 case 4: dbl=Double.valueOf(item); row.add((dbl < 0.001 && dbl >-0.001) ? "" : nform2.format(dbl)); break; } } --19 data.add(row); } --20 } catch (SQLException e) { e.getMessage()); } --21 finally { --22 if (pStat != null) { --23 try { pStat.close(); } --24 catch (SQLException e) { e.getMessage(); } --25 try { CONN.close(); } --26 catch (SQLException e) { e.getMessage; }} } --27 return data; --28 }
Kódu je ve funkci celkem dost (ale jenom o 6 řádků více, než v původní verzi v pátém dílu, tedy 28 vs 22), takže se na něj podíváme trochu podrobněji:
- 1. do parametrů funkce přibylo pole celočíselných hodnot. Jejich význam si ukážeme později
- 2. zde je vše při starém
- 3. ani tady se nic nemění
- 4. nová deklarace textové proměnné
- 5. nová deklarace celočíselné proměnné
- 6. nová deklarace desetinné proměnné
- 7. již známé přihlášení k databázi
- 8. beze změny, příprava dotazu
- 9. provedení dotazu a uložení do příslušné proměnné také beze změn
- 10. start procházení výsledků dotazu je také stejný
- 11. deklarace řádku údajů opět ve známém formátu
- 12. zahájení smyčky FOR je nové a souvisí se změnou funkce na obecnou. Prochází se přes deklarovanou proměnnou, která představuje počet sloupců, které jsou výsledkem dotazu a vyjadřuje také jejich typ
- 13. do deklarované textové proměnné se uloží výsledek dotazu pro každý sloupec v obecném formátu Object a převede se na typ String
- 14. do deklarované proměnné se uloží požadovaný typ (vlastně budoucí formát zobrazení) příslušného sloupce výsledné datové sady
- 15. pomocí této proměnné se určí typ sloupce, nastaví se jeho formát a tato hodnota se uloží do proměnné pro každý řádek výsledného dotazu. Pro typ 0 se neděje nic a hodnota se uloží jako String
- 16. typ 1 znamená oříznutí nadbytečných prázdných znaků zprava pomocí deklarované funkce
- 17. typ 2 znamená převod na formát pro uložení např, finančních údajů – desetinné číslo se dvěma viditelnými desetinnými místy
- 18. typ 3 je pak formátován jako běžné datum v národním formátu – DD.MM.YYYY
- 19. typ 4 nahradí malá desetinná čísla prázdným znakem (opět se může hodit pro nějaké finanční a peněžní hodnoty). Za tímto formátováním končí jednak přepínač podle typu sloupce, ale také smyčka pro všechny sloupce dotazu
- 20. uložením jednotlivých řádků do proměnné s obsahem datové sady se také ukončí procházení výsledků dotazu
- 21. – 28. ukončení funkce se zachycením výjimek a návratová proměnné jsou opět stejné
Další změnu uděláme v proceduře pro inicializaci jednotlivých sloupců tabulky. Jak již bylo řečeno, zde je zobecnění mnohem jednodušší:
private void initTableColumn(final TableColumn[] column) { --1 for (int i=0;i<column.length;i++) { --2 final int finalI = i; --3 column[i].setCellValueFactory(new Callback<TableColumn.CellDataFeatures<ObservableList<String>, String>, ObservableValue<String>>() { --4 @Override public ObservableValue<String> call(TableColumn.CellDataFeatures<ObservableList<String>, String> param) { --5 return new SimpleStringProperty(param.getValue().get(finalI)); --6 } }); } }
Zde přibyl pouze jeden řádek proti původní verzi, kterou jsme ale nekomentovali. Proto to uděláme teď:
- 1. do parametrů se přidá deklarace pole s typem tabulkového sloupce
- 2. zahájí se smyčka přes počet deklarovaných sloupců
- 3. definuje se lokální proměnná tak, aby bylo možné použít parametr smyčky v dalším kódu funkce
- 4. konkrétní sloupec je zde nahrazen hodnotou deklarovaného pole a všechny parametry jsou typu String bez ohledu na původní typ sloupce
- 5. i zde jsou všechny parametry typu String
- 6. vzhledem k tomu, že jsou předchozí parametry všechny stejné, je možné zde použít pouze jeden typ funkce a přiřadit příslušný sloupec pomocí deklarované proměnné
Poslední novou procedurou je pak procedura na zarovnání tabulkových hodnot ve sloupcích. Ani zde nebylo zobecnění nijak složité:
private void alignTableColumn(final TableColumn[] column, final Integer[] pos) { --1 for (int i=0;i<column.length;i++) { --2 final int finalI = i; --3 column[i].setCellFactory(new Callback<TableColumn<ObservableList<String>, String>, TableCell<ObservableList<String>, String>>() { --4 @Override public TableCell<ObservableList<String>, String> call(TableColumn<ObservableList<String>, String> param) { --5 TableCell<ObservableList<String>, String> tc = new TableCell<ObservableList<String>, String>() { --6 @Override public void updateItem(String item, boolean empty) { --7 if (item != null) { setText(item); } } }; --8 switch (pos[finalI]) { --9 case 1: tc.setAlignment(Pos.CENTER_LEFT); break; --10 case 2: tc.setAlignment(Pos.CENTER); break; --11 case 3: tc.setAlignment(Pos.CENTER_RIGHT); break; } --12 return tc; --13 } }); } }
Zde jsou nově tři řádky a podrobnější komentář následuje:
- 1. do parametrů se přidají deklarace pole tabulkových sloupců a celočíselné pole
- 2. zahájí se smyčka přes všechny sloupce tabulky
- 3. definuje se lokální proměnná tak, aby bylo možné použít parametr smyčky v dalším kódu funkce
- 4. pro každý sloupec se nastaví příslušná vlastnost, všechny parametry jsou opět textové
- 5. – 8. další deklarace jsou prakticky stejné
- 9. pro přepínač se použije deklarovaná proměnná pole s parametrem smyčky
- 10. pro hodnotu 1 se provede zarovnání nalevo
- 11. pro hodnotu 2 se provede zarovnání na střed
- 12. pro hodnotu 3 se provede zarovnání napravo
- 13. návratová hodnota je stejná
Nové verze procedur už sice máme připravené, ale naše snažení ještě není na úspěšném konci. Už jenom z toho důvodu, že IJI hlásí spoustu chyb. Některé z nich je možné odstranit importem potřebných knihoven, ale určitě ne všechny. Hlavně chyby v posledních dvou procedurách mají souvislost s deklarací tabulky a jejích sloupců. Už bylo dříve řečeno, že všechny sloupce budou deklarovány úplně stejně (typ String) bez ohledu na jejich typ. Pro lepší přehled si sloupce také seřadíme:
@FXML private TableView<ObservableList<ObservableList>> table_Obce; @FXML private TableColumn<ObservableList<String>, String> col_ID; @FXML private TableColumn<ObservableList<String>, String> col_Ckraje; @FXML private TableColumn<ObservableList<String>, String> col_Kraj; @FXML private TableColumn<ObservableList<String>, String> col_COkresu; @FXML private TableColumn<ObservableList<String>, String> col_Okres; @FXML private TableColumn<ObservableList<String>, String> col_Obec;
Ani tohle ale ještě nevede k vítěznému závěru a IJI pořád tvrdošíjně lpí na chybách. Proto musíme do třídy samexam2 zkopírovat z předchozí verze třídy samexam1 jednak obě procedury na připojení k databázi a kontrolu úspěšnosti tohoto připojení (conDB, logOK) a také obě globální proměnné:
private Connection CONN = null; private String host = "jdbc:postgresql://127.0.0.1:5432/fxguidedb";
K tomu musíme přidat ještě funkci pro formátování hodnot v desetinném tvaru:
public NumberFormat nform2 = new DecimalFormat("#,##0.00");
Toto je samozřejmě pouze jedna z možností a ukázka formátování. Obecně lze asi konstatovat, že výše uvedené obecné provedení dává tvůrcům a uživatelům poměrně dobré a jednoduché nástroje pro formátování výsledných datových sad dle jejich potřeb.
Poslední dvě změny, které musíme udělat, je vytvoření procedury pro volání všech výše uvedených funkcí a procedur a spuštění této procedury při otevření formuláře:
private void showData() { final String query = "SELECT * FROM obce ORDER BY ckraje, cokresu;"; --1 final ObservableList data = dataView(query, new Integer[]{1,0,1,0,1,1}); --2 final TableColumn[] tc = new TableColumn[]{col_ID, col_Ckraje, col_Kraj, col_Cokresu, col_Okres, col_Obec}; --3 initTableColumn(tc); --4 alignTableColumn(tc, new Integer[]{2,2,1,2,1,1}); --5 table_Obce.setItems(data); --6 }
Řádků sice není příliš mnoho, ale přesto k nim přidáme krátký komentář:
- 1. v definici dotazu bylo použito řazení dle čísel krajů a okresů
- 2. datová sady pro zobrazení je deklarována pomocí předchozího dotazu a proměnné, kde je celkem 5 sloupců s deklarací požadovaného typu (formátu) zobrazení
- 3. pole tabulkových sloupců je deklarováno pomocí proměnných z FXML
- 4. inicializace sloupců tabulky je volaná pouze jedním příkazem
- 5. zarovnání zobrazených hodnot v tabulce stačí volat také pouze jednou. jako parametry jsou použitá pole pro definici tabulkových sloupců a pole pro typ jejich zarovnání v zobrazované tabulce
- 6. přiřazení darové sady příslušnému widgetu je pak opět stejné, jako v úvodním příkladu
Nyní už by měl být vše v pořádku, takže můžeme změny v kódu uložit a přeložit a spustit příslušnou aplikaci. Když si vybereme nově definovanou úlohu, tak uvidíme, že je vše v pořádku a dostaneme výsledek, který je viditelný na následujícím obrázku:
V příloze jsou také příslušné nové třídy: samExam2.fxml, samexam2.java.
V dnešním dílu jsme představili konkrétní obecnější řešení implementace a komentovali podrobněji celý kód. Konečně jsme si také ukázali kompletní obsah tabulky ukázkových dat. V příštím dílu se budeme věnovat pokročilejším funkcím PG jako jsou pohledy a uložené procedury. Samozřejmě si také ukážeme, jak tyto funkce využít v rámci aplikace JavaFX.