Programování v JavaFX: zobrazení a formátování dat z databázové tabulky (2)

Jaromír Vojtaj 8. 10. 2015

Minulý šestý díl byl zaměřen na kód potřebný pro základní zobrazení dat z tabulky ve formuláři a nastínil také možná obecnější řešení této problematiky. V dnešním sedmém dílu budeme obecnější řešení implementovat a komentovat a konečně si ukážeme kompletní obsah tabulky ukázkových dat.

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:

widgety

V příloze jsou také příslušné nové třídy: samExam2.fxmlsame­xam2.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.

Našli jste v článku chybu?
Vitalia.cz: 5 chyb, které děláme při skladování potravin

5 chyb, které děláme při skladování potravin

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Lupa.cz: Blíží se konec Wi-Fi sítí bez hesla?

Blíží se konec Wi-Fi sítí bez hesla?

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Podnikatel.cz: Slevu na dani na EET neuplatní každý

Slevu na dani na EET neuplatní každý

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Podnikatel.cz: Takhle se prodávají mražené potraviny

Takhle se prodávají mražené potraviny

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

Vitalia.cz: Test dětských svačinek: Tyhle ne!

Test dětských svačinek: Tyhle ne!

Vitalia.cz: Fyzioterapeutka: Chůze naboso? Rozhodně ano!

Fyzioterapeutka: Chůze naboso? Rozhodně ano!

DigiZone.cz: Digi Slovakia zařazuje stanice SPI

Digi Slovakia zařazuje stanice SPI

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

DigiZone.cz: Rapl: seriál, který vás smíří s ČT

Rapl: seriál, který vás smíří s ČT

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

DigiZone.cz: Samsung EVO-S: novinka pro Skylink

Samsung EVO-S: novinka pro Skylink