Hlavní navigace

Programování v JavaFX: zobrazení různých typů dat, úvod do CRUD

29. 10. 2015
Doba čtení: 11 minut

Sdílet

V minulém dílu jsme ukončili kontrolou přítomnosti potřebných součástí databáze a ukázkou zobrazení tabulkových dat pomocí pohledů jednu větší kapitolu našeho seriálu. Od dnešního dílu se začneme věnovat uživatelským změnám ukázkových dat – přidávání nových, mazání stávajících a změny aktuálních záznamů a hodnot.

Dostáváme se tedy konečně k již dříve uvedenému „systému“ CRUD. Jedná se vlastně o zkratku, která v sobě zahrnuje 4 základní operace s databázovými záznamy (obecně se dá říci, že s jakýmkoliv záznamem uloženým v příslušném úložišti). Doposud jsme se věnovali pouze jedné operaci – zobrazení záznamů z databáze v tabulce (READ). Nyní si postupně ukážeme, jaké jsou v JavaFX aplikacích možnosti pro využití dalších operací: vytvoření nového záznamu (CREATE), změny a úpravy stávajících záznamů (UPDATE) a mazání vybraných záznamů (DELETE). Pro tyto účely využijeme novou tabulku udaje, kterou jsme si definovali pomocí PG funkce. Jenom pro osvěžení paměti uvedeme její datovou strukturu:

CREATE TABLE udaje (
id smallint NOT NULL,
celecis integer NOT NULL,
descis numeric (12,2) NOT NULL,
maledes numeric (8,6) NOT NULL,
retezec character (32) NOT NULL,
datum date NOT NULL,
CONSTRAINT udaje_id UNIQUE(id))
WITH (OIDS=FALSE);

Jak je z datové struktury patrné, tabulka má celkem 6 sloupců:

  1. id – malé celé číslo, které je zároveň primárním klíčem tabulky
  2. celecis – celé číslo v plném rozsahu
  3. descis – desetinné číslo s nastaveným rozsahem a počtem desetinných míst
  4. maledes – opět desetinné číslo, které je velmi blízké nule a má vysoký počet desetinných míst
  5. retezec – textová hodnota s pevně nastaveným rozsahem
  6. datum – hodnota datumu v běžném formátu a rozsahu

Pokud by měl někdo zájem, může si v minulých dílech použité deklaraci PG funkce (initData) najít způsob a rozsah hodnot, kterými je tabulka primárně naplněná. Než se pustíme do samotné práce a ukážeme si další operace nad databázovými záznamy, musíme si připravit základ v naší aplikaci. Bude to představovat několik již známých kroků:

  • vytvořit nový FXML soubor, který bude obsahovat všechny potřebné widgety
  • vytvořit novou proceduru, která bude obsahovat kompletní kód pro operace CRUD
  • postupně vytvořit kód pro všechny 4 operace
  • novou verzi aplikace přeložit, spustit a ukázat funkční operace

Jako první si vytvoříme nový FXML soubor, který pod názvem samExam4.fxml uložíme do adresáře GUI-Files. Soubor bude mít podobnou strukturu, jako měl ten předchozí. Použijeme opět tři záložky, kdy první bude obsahovat tabulku pro zobrazení dat, druhá bude obsahovat funkce pro mazání a aktualizace záznamů a třetí pak funkce pro přidání nového záznamu. Abychom zvýšili přehlednost, budeme FXML soubor vytvářet postupně tak, jak budeme potřebovat v postupu tvorby kódu. V první fázi provedeme tyto kroky:

  1. otevřeme JFXSB, založíme nový soubor a uložíme ho dle popisu výše
  2. do názvu třídy kontroléru vložíme hodnotu jfxapp.samexam4
  3. přidáme kořenový widget AnchorPane, k němu přiřadíme příslušný styl pomocí souboru main.css, velikost na 1000 × 730 a název contentPane
  4. přidáme widget TabPane, zvýšíme počet záložek na 3 a upravíme jejich nadpisy
  5. do první záložky přidáme widget TableView, zvětšíme ho na celou plochu a vložíme do widgetu ScrollPane
  6. zvýšíme počet sloupců na 6, vložíme jejich názvy a první nástřel šířky sloupců
  7. vložíme název tabulky (table1) a všech šesti sloupců (col1 – col6)
  8. vytvoříme kostru kontroléru (Full) a uložíme její kopii

Výsledný vzhled částečného GUI je na prvním obrázku první galerie. Pro první fázi našeho dalšího příkladu by mohl být rozsah FXML souboru dostatečný, a proto se pustíme do jeho začlenění do naší ukázkové aplikace:

  1. v IJI přidáme novou třídu samexam4.java a vložíme do ní kód kontroléru
  2. ve třídě mainForm přidáme novou třídu showExam4 pro volání okna samexam4
  3. k akci příslušného tlačítka přidáme volání nově vytvořené procedury
  4. uložíme změny, aplikaci přeložíme a spustíme. Vše by mělo být v pořádku a výsledné okno nové úlohy vidíme na druhém obrázku v první galerii.

Nově vytvořenou úlohu máme tedy připravenou a umíme jí zavolat z aplikace. Zatím v ní není obsažena žádná z našich požadovaných úloh, takže se pustíme do té první a zobrazíme si data z tabulky udaje. K tomu využijeme již vytvořené funkce procedury ze třídy samexam3 a do nové třídy samexam4 zkopírujeme následující položky:

  • deklarace proměnných CONN, host, nform2
  • funkce connDB, dataView, initTableColumn, aligntableColumn

Jako poslední musíme nově vytvořit třídu pro zobrazení dat z tabulky a zavolat jí při otevření okna ukázkové úlohy. Pro novou třídu můžeme samozřejmě použít kostru z minulého příkladu a její kód bude nakonec vypadat asi takto:

 private void showRaw() {
        final String query = "SELECT * FROM udaje;";            //1
        final ObservableList data = dataView(query, new Integer[]{0,0,0,0,0,0});            //2
        final TableColumn[] tc = new TableColumn[]{col1, col2, col3, col4, col5, col6};         //3
        initTableColumn(tc);            //4
        alignTableColumn(tc, new Integer[]{1,1,1,1,1,1});           //5
        table1.setItems(data);          //6
    }

Sice jsme to již dělali, ale z určitého důvodu si znovu okomentujeme kód této procedury:

  1. řádek – zadáváme jednoduchý dotaz na všechny záznamy v tabulce bez příkazu pro řazení záznamů
  2. řádek – zde necháváme data úplně neformátovaná a zcela v původním stavu, jak jsou uložená v tabulce. To nám zajistí celočíselné pole, kde je zvoleno formátování beze změny
  3. řádek – vytvoříme pole sloupců dle jejich názvů z kostry kontroléru
  4. řádek – běžná inicializace sloupců tabulky
  5. řádek – zarovnání nastavuje všude nalevo
  6. řádek – do tabulky dle názvu kostře kontroléru přiřazujeme data

Proceduru pro zobrazení dat máme připravenou a pro úspěšné provedení dané operace musíme udělat ještě dva jednoduché kroky:

  • do procedury initialize vložit volání procedury showRaw. To nám zajistí zobrazení tabulky naplněné daty při otevření okna ukázkové úlohy
  • do deklarací tabulek a sloupců z FXML souboru doplníme příslušné typy:
        @FXML private TableView<ObservableList<ObservableList>> table1;
        @FXML private TableColumn<ObservableList<String>, String> col1;
        ...

Nyní již můžeme aplikaci přeložit a spustit. Vše by mělo být v pořádku a okno s tabulkovými daty můžeme vidět na třetím obrázku první galerie. Na první pohled si můžeme všimnout zarovnání všech sloupců nalevo, jak jsme deklarovali výše. Jak je z obrázku zřejmé, není toto zarovnání pro náš vzorek dat úplně nejlepší, takže provedeme změnu. Uděláme to ale ještě trochu jinak a vytvoříme si novou proceduru showFmt (použijeme kopii a změnu názvu procedury showRaw) a odkážeme se na její spuštění při otevření okna. V nové proceduře změníme pouze jeden řádek, kde je nastaveno zarovnání sloupců:

alignTableColumn(tc, new Integer[]{2,3,3,3,1,2});

Výsledek jednoduché změny je vidět na čtvrtém obrázku v první galerii. První sloupec ID je zarovnán na střed, další tři sloupce s čísly napravo, textový sloupec nalevo a datum opět na střed. Asi je na první pohled jasné, že nový vzhled a formát tabulky je lepší, než byl ten předchozí. Následně se tedy můžeme podívat na samotný formát hodnot v jednotlivých sloupcích. První sloupec je asi jasný a není s ním potřeba nic provádět. Jeho formát tedy zůstane beze změny. Totéž platí o druhém sloupci, kde jsou celá čísla v rozsahu 10 – 260. Třetí sloupec, kde jsou desetinná čísla stejného rozsahu, by mohl být ale trochu zajímavější. Můžeme totiž celkem často narazit na situaci, kdy chceme desetinné číslo v jiném formátu uložit do databáze a v jiném zobrazit uživateli. V našem konkrétním příkladě máme připravené formátování na dvě desetinná čísla, aby to odpovídalo běžným peněžním hodnotám. Abychom dosáhli nějakého efektu, tak si provedeme změnu struktury tabulky udaje, konkrétně pak sloupce descis:

descis numeric (12,4) NOT NULL,

Tabulku znovu vytvoříme a naplníme daty pomocí PG funkce, nejlépe v aplikaci pgAdmin. Výsledný vzhled je pak vidět na pátém obrázku první galerie. Jak je vidět, počet desetinných míst se zobrazil v plném rozsahu. Zkusíme tedy udělat první změnu ve formátování třetího sloupce a zobrazíme ho jenom se dvěma desetinnými čísly. K tomu přidáme ještě první dva sloupce a příslušný řádek procedury showFmt bude vypadat asi takto:

final ObservableList data = dataView(query, new Integer[]{0,0,2,0,0,0});

Výsledek je vidět na šestém obrázku v první galerii. Záměrně je zde zvýrazněn třetí řádek. Pokud se podíváme na předchozí obrázek, tak zjistíme, že je zde uložena hodnota 56.4778. Po formátování je ale zobrazena hodnota 56.48, což je zřejmě hodnota, která je zaokrouhlená z původního (reálně uloženého) čísla v tabulce. Jedním krokem se nám tedy podařilo zajistit více funkcí:

  • v tabulce jsou data uložená v původním rozsahu a pokud je to nutné, mohou se tato čísla používat k případným výpočtům
  • při zobrazení tabulky dojde k formátování a zároveň zaokrouhlování uložených hodnot. Změnou proměnné nform2, přidáním další proměnné s jiným formátem a dalších možností ve funkci viewData můžeme využít prakticky libovolný formát desetinných čísel.
  • formátované hodnoty máme uložené v lokální proměnné a můžeme je tedy použít k dalším účelům v aplikaci

Tím se můžeme přesunout ke čtvrtému sloupci, kde jsou malá desetinná čísla. Samo o sobě by to nebyl problém, ale zkusíme si ukázat, jaké jsou možnosti. Jako první si ukážeme tu, která není skoro vůbec zajímavá:

final ObservableList data = dataView(query, new Integer[]{0,0,2,2,0,0});

Sedmý obrázek první galerie ukazuje výsledný vzhled tabulky. Je asi jasné, že při daném rozsahu uložených hodnot a použitém formátování je výsledek odpovídající. Ve sloupci jsou pouze nulové hodnoty se dvěma desetinnými místy. Zkusíme tedy ještě další změnu a uvidíme, jak bude vypadat její důsledek:

final ObservableList data = dataView(query, new Integer[]{0,0,2,4,0,0});

Výsledek změny formátování vidíme na osmém obrázku v první galerii. Jak je z něj zřejmé, jedná se o změnu dosti zásadní – ve sloupci nejsou viditelné vůbec žádné hodnoty. Je to ale výsledek správný, protože jsme použili formátování, které funguje takto: pro čísla v rozsahu od –0.001 do +0.00.1 se zobrazí prázdný řetězec, pro ostatní čísla se zobrazí zaokrouhlené hodnoty na dvě desetinná místa. Tímto (či podobným) způsobem můžeme ze zobrazení „vyloučit“ vybraný rozsah hodnot a např. tak zvýšit přehlednost tabulky.

Předposlední sloupec nám dává dvě základní možnosti. Jedna z nich spočívá v nulovém formátování, kdy se řetězec zobrazí přesně tak, jak je uložený v databázi. Druhou variantu pak představuje oříznutí prázdných znaků zprava. Tato možnost může být zajímavá všude tam, kde se řetězce zadávají uživatelsky a "ručně"a kde není zaručené, že neobsahují zbytečné prázdné znaky. Příkaz by pak vypadat takto:

final ObservableList data = dataView(query, new Integer[]{0,0,2,4,1,0});

V posledním sloupci je uložené datum. Jak je z předchozích obrázků patrné, je uloženo a také zobrazeno v anglosaském formátu. To samo o sobě by nebylo úplně špatně, ale v našich končinách jsme přece jenom zvyklí na něco trochu jiného. Proto použijeme poslední připravený typ formátování:

final ObservableList data = dataView(query, new Integer[]{0,0,2,4,1,3});

Jak ukazuje devátý obrázek první galerie, výsledek odpovídá našim představám o tom, jak vypadá pro nás datum ve „stravitelném“ formátu. I zde by samozřejmě bylo možné použít jiný způsob (aktuálně je použita jednoduchá řetězcová funkce). Jedna z obecných možností je popsána např. zde: TableView Cell Renderer.

Jiné způsoby nabízí také přímo JavaFX, kde jsou k dispozici funkce pro převod jednotlivých formátů mezi sebou. Zájemci by mohli hledat pod hesly SimpleDateFormat, DateFormat, DateStringConverter, DateTimeStringConverter, atd. Pro naše účely se tato řešení moc nehodí hlavně proto, že máme hodnotu z tabulky již převedenou na řetězec a výsledný formát musí být také řetězec. Proto byl zvolen aktuální postup, ale nikde není napsáno, že by nebylo možné použít třeba dvojí konverzi string → datum → string. Jak už to tak bývá, nic není úplně zadarmo a naše zobecnění při zobrazení dat z tabulky má i některé stinné stránky. Tímto máme tedy splněnou první průběžnou úlohu a vyřešenou operaci zobrazení dat z tabulky (READ). V dnešním dílu se už do další operace pouštět nebudeme, ale ukážeme si řešení, které k tomu budeme určitě potřebovat. Jedná se o následující dílčí úlohu:

  • když už máme k dispozici tabulku se zobrazením dat z databáze, bylo by vhodné nějak vybírat jednotlivé záznamy v této tabulce
  • když už máme nějaký záznam vybraný, bylo by vhodné z něj „vydolovat“ vybrané nebo všechny údaje tak, abychom se zbytečně nemuseli znovu dotazovat na tabulku

Obě tyto úlohy jsou v rámci JavaFX aplikace řešitelné a my si ukážeme, jak na to. Jako první si vytvoříme novou proceduru, která nám zobrazí hodnotu ve sloupci ID z vybraného záznamu. Zobrazení zatím proběhne pouze do konzole, ale pro ukázkový příklad to bude stačit. Kód bude vypadat asi takto:

private void tableClick() {
   System.out.println(String.valueOf(table1.getSelectionModel().getSelectedItems().get(0).get(0))); }

První část příkazu je zřejmá a zajišťuje zobrazení hodnoty v konzole. Pak následuje převod do řetězce (String.valueOf). V parametrech je pak konečně samotný výkonný příkaz, který obsahuje 5 částí:

  1. odkaz na příslušnou tabulku – table1
  2. odkaz na funkci, která vrací hodnotu typu výběru položek – viz getSelectionModel
  3. odkaz na funkci, které vrací dvojrozměrný list s hodnotami položek ve výběru – viz getSelectedItems
  4. hodnota z prvního „řádku“ ve výběru, resp. listu. V našem případě se bude vybírat pouze jeden řádek z tabulky pomocí dvojkliku myši, takže bude vždy použita hodnota 0
  5. hodnota z daného „sloupce“ výběru (koresponduje s příslušným sloupcem tabulky). Pro zobrazení sloupce ID tedy bude také hodnota 0.

Druhý příkaz vložíme do procedury initialize a bude to handler, který bude reagovat na dvojklik myši na vybraný řádek tabulky a vyvolávat proceduru tableClick. Jeho kód bude vypadat následovně:

table1.setOnMouseClicked(new EventHandler<MouseEvent>() {                 //1
            @Override
            public void handle(MouseEvent mouseEvent) {                     //2
                if (mouseEvent.getButton().equals(MouseButton.PRIMARY)) {   //3
                    if (mouseEvent.getClickCount() == 2) {                  //4
                        tableClick();                   //5
                    }
                }
            }
        });
    }

Okomentujeme pouze 5 vybraných řádků:

CS24_early

  1. řádek – pro příslušnou tabulku se vytváří handler, který reaguje na kliknutí myši a deklaruje se příslušná událost
  2. řádek – deklaruje se funkce pro zpracování události
  3. řádek – zjišťuje se, jestli došlo ke kliknutí levým tlačítkem myši (varianty jsou MIDDLE, NONE, PRIMARY a SECONDARY)
  4. řádek – pokud došlo ke kliku levým tlačítkem, zjišťuje se počet kliknutí. V tomto případě je hodnota funkce nastavena na dvojklik
  5. řádek – pokud byl proveden dvojklik, volá se příslušná procedura

Změny v kódu uložíme, přeložíme a spustíme aplikaci. Pokud si vybereme např. řádek s číslem 22 a uděláme na něm dvojklik, tak na posledním obrázku v první galerii vidíme, že se toto číslo zobrazí v konzole. Tímto dnešní díl ukončíme a pro lepší přehled dáme do přílohy dosavadní kód příslušné procedury - samexam4.java.

V dnešním dílu jsme se zaměřili na vytvoření nové ukázkové úlohy. Tato úloha patří do balíku uživatelských změn záznamů v databázi – přidávání nových, mazání stávajících a změny aktuálních záznamů a hodnot. Ukázali jsme si možnosti formátování různých typů dat a jejich zobrazení. Jako poslední byl uveden způsob výběru záznamů z tabulky a využití zobrazených hodnot. V příštím dílu se budeme věnovat dalším operacím ze systému CRUD, které nám ještě zbývají.

Byl pro vás článek přínosný?