Umíme to s Delphi: 83. díl – databázová aplikace s grafy

Před týdnem jsme poznali základní komponentu pro vytváření grafů – komponentu Chart. Dnes začneme popisem a vysvětlením některých fragmentů příkladu ze závěru minulé části, a pak se budeme zabývat databázovou „kolegyní“ komponenty Chart: uvidíme, jak zajistit grafické výstupy z databází pomocí komponenty DBChart.
Pojďme si nejprve společně prohlédnout a vysvětlit klíčové fragmenty zdrojového kódu, který jsme vytvořili v minulém dílu seriálu. Pokud jste předchozí část seriálu nečetli (nepředpokládám však, že některý náhodný čtenář začne čtení seriálu jeho 83. dílem :-)), prosím vám, abyste tak nejprve učinili, neboť dnešní díl je opravdu jejím pokračováním.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TeEngine, Series, ExtCtrls, TeeProcs, Chart;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Chart1: TChart;
    Series1: TPieSeries;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    List1, List2: TStringList;
  public
    { Public declarations }
  end;
  ...

V deklaraci třídy formuláře jsme si vytvořili dva privátní atributy třídy TstringList: List1 a List2. V List1 uchováváme přípony, v List2 pak jejich četnosti. Tento problém by bylo možné vyřešit jistě elegantněji, nicméně pro naše účely uvedená struktura bohatě postačuje.

Pro uchovávání řetězců by se nám hodila třída TStrings, kterou známe z nejrůznějších vlastností Items (např. komponent ListBox apod.). Ohledně tohoto tématu jsem již od čtenářů obdržel několik emailových dotazů, proto bych rád uvedl na pravou míru, že třídu Tstrings nelze v našich aplikacích běžně používat, neboť se jedná o abstraktní třídu, takže pokus o vytvoření její instance způsobí výjimku. Na základě této abstraktní třídy je v Delphi definována řada jiných tříd, které jsou již plně využitelné a s nimiž lze pracovat velmi podobně jako s Tstrings. Jednou z těchto tříd je TstringList, kterou používáme v naší aplikaci.

V deklaraci třídy tedy uvedeme dva atributy typu TStringList. Budeme je potřebovat po klepnutí na tlačítko Button1, až začneme celou operaci. Proto v obsluze události OnClick tohoto tlačítka oba seznamy vytvoříme:

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;

begin
  List1 := TStringList.Create;
  List2 := TStringList.Create;
  ...

Po skončení práce (což bude v okamžiku, kdy bude graf zobrazen a zdrojová data uložená v seznamech tedy již nebudou potřeba) oba seznamy zase uvolníme. Tuto operaci provedeme na konci obsluhy téže události OnClick tlačítka Button1:

  ...
  List1.Free;
  List2.Free;
end;

Dalším zajímavým fragmentem aplikace je procedura Vyhledej, jejímž úkolem je prohledat disk (od zadaného adresáře, včetně podadresářů), nalézt všechny soubory a „správně“ naplnit oba seznamy. Vyhledávání souborů je z principu rekurzivní operace, proto celá procedura bude rekurzivní. Protože jsme se v našem seriálu rekurzi dosud nevěnovali, provedeme malou odbočku a vysvětlíme si její podstatu. Pokud je vám princip rekurze dostatečně znám, můžete následující podkapitolu směle přeskočit.

Rekurzivní algoritmy

Rekurzivní podprogram je podprogram, který volá sebe sama.

Význam rekurzivních funkcí je vidět především u problémů, které jsou „přirozeně“ rekurzivní. Jako příklad bývá často zmiňován algoritmus pro výpočet faktoriálu

(i když rekurzi lze vypočítat i bez použití rekurzivního volání). Připomeňme, že faktoriál čísla

n se značí n! a platí pro něj následující vztah (hvězdička znamená násobení):

n! = n * (n - 1) * (n - 2) * ... * 2

Příklad: faktoriál čísla 5:

5! = 5 * 4 * 3 * 2 = 120

Pojďme vztah pro výpočet faktoriálu důkladně analyzovat. Prohlédneme-li si jej dobře, snadno zjistíme, že jej lze vyjádřit též prostřednictvím následujících dvou vztahů:

n! = n * (n - 1)!
0! = 1;

Jinak řečeno – faktoriál z čísla n počítáme jako (n * faktoriál_z_čísla_o_jedna_nižší). Faktoriál počítáme pomocí faktoriálu – typická situace pro využití rekurzivního podprogramu:

function Faktorial(n: integer): double;
begin
  if n = 0 then
    Faktorial := 1
  else
    Faktorial := n * Faktorial(n - 1);
end;

Funkce volá opakovaně sebe samu; při každém novém volání sama sobě předá nižší číslo, z něhož se počítá faktoriál. Pozor je nutné dávat na správné stanovení

"zastavující podmínky" rekurze. V předchozím příkladu byla touto "stopkou" podmínka

if n = 0. Kdyby nebyla zastavující podmínka uvedena vůbec nebo by byla stanovena chybně, volala by funkce sama sebe donekonečna, což by dříve nebo později způsobilo zaplnění operační paměti a zatuhnutí programu.

Jedním z největších problémů rekurze je její značná paměťová náročnost. Každé volání funkce je spojeno s kopírováním mnoha informací do paměti a ani s tím spojené časové náklady nejsou zanedbatelné.

Zpět k popisu aplikace – vyhledávací algoritmus

Vyhledávací algoritmus pro prohledávání disku je již velmi vzdálen od náplně dnešního dílu, proto si jej popíšeme opravdu velmi stručně. Funkci Vyhledej koncipujeme robustněji než je pro naše účely nezbytně nutné: přestože víme, že budeme vyhledávat všechny soubory všech přípon <.>, předáme proceduře masku pomocí parametru.

Celé vyhledávání tedy probíhá tak, že se pokusíme vyhledat všechny výskyty požadovaného souboru v předaném adresáři pomocí funkcí FindFirst a FindNext. Potom bez ohledu na výsledek vyhledávání prohledáme znovu aktuální adresář a najdeme první podadresář. Pak znovu (rekurzivně) zavoláme naši funkci pro vyhledávání a jako parametr jí předáme nalezený adresář. Tak pokračujeme až do okamžiku, kdy budou prohledány všechny podadresáře zadaného adresáře.

Jakmile nalezneme nějaký soubor, jehož přípona není prázdná (takové soubory do srovnání nezahrneme), zjistíme, zda se příslušná přípona nalézá v seznamu List1. Pokud ne, přidáme jej tam. Pokud přípona v seznamu je, inkrementujeme hodnotu uvedenou na téže pozici v druhém seznamu List2.

      if Form1.List1.IndexOf(ExtractFileExt(Nalezeno.Name)) = -1 then
        begin  // jeste v seznamu neni
          Form1.List1.Add(ExtractFileExt(Nalezeno.Name));
          Form1.List2.Add(`1`);
        end
        else
        begin
          Pom := StrToInt(Form1.List2.Strings: Invalid JSON primitive: Form1.List1.IndexOf.-->);
          Inc(Pom);
          Form1.List2.Strings: Invalid JSON primitive: Form1.List1.IndexOf.--> := IntToStr(Pom);
        end;

Poslední fragment, který rozebereme, je finální vytvoření grafu. K tomu dojde uvnitř obsluhy události OnClick tlačítka Button1 – po návratu z procedury Vyhledej:

  Button1.Caption := `Start`;
  Button1.Enabled := False;

  Caption := `Moment, vyhledavam...`;
  Vyhledej(`c:\dokumenty\*.*`);
  Caption := `Struktura souboru na disku`;
  Button1.Enabled := True;

  Series1.OtherSlice.Style := poBelowPercent;
  Series1.OtherSlice.Value := 3;
  Series1.OtherSlice.Text := `Ostatni`;

  for I := 0 to List1.Count-1 do
    Series1.Add(StrToInt(List2.Strings[I]), List1.Strings[I]);

Nejprve nastavíme hodnoty Style, Value, Text vlastnosti OtherSlice sérii Series1. Tyto hodnoty slouží ke stanovení části grafu, do níž mají být zahrnuty všechny zbývající prvky neuvedené v ostatních kategoriích. Pomocí Style = poBelowPercent říkáme, že „prahovou“ hodnotu pro ostatní stanovíme procentuálně; Value = 3 stanovuje, že všechny prvky, jejichž četnost je nižší než 3 procenta, budou sdruženy do jediného prvku a Text = Ostatni říká, že tento prvek se bude nazývat Ostatni.

Poté pouze v cyklu for projdeme pole List1 a příslušné hodnoty List1 a List2 zobrazíme v grafu.

Pokud chceme, aby přípony a procentuální poměry jejich četností byly uvedeny v legendě i v popisku, stačí otevřít editor Edit Chart a „pohrát“ si se záložkou Legend (a podle požadavků i s dalšími záložkami).

Graf jako výstup z databáze – komponenta DBChart

Grafy ovšem nemusíme používat jen pro nedatabázové aplikace; pro databáze je v Delphi k dispozici komponenta DBChart (nalézá se v paletě DataControls), která je běžnému grafu velmi podobná, ovšem výborně spolupracuje s databází.

Abychom si ukázali, jak DBChart použít, vytvoříme opět společně jednoduchou databázovou aplikaci.

1. Vytvořte novou aplikaci, na formulář umístěte komponenty DataSource (DataSource1), Table (Table1), DBGrid (DBGrid1), Button (Button1), RadioGroup (RadioGroup1) a DBChart (DBChart1).

2. Komponentě Table nastavte vlastnost DatabaseName na DBDEMOS (využijeme ukázkovou databázi, která je dodávána společně s Delphi) a vlastnost TableName nastavte na country.db. Pak nastavte vlastnost Active na True.

3. Komponentě DataSource nastavte vlastnost DataSet na Table1.

4. Komponentě DBGrid nastavte vlastnost DataSource na DataSource1.

5. Pak otevřete editor vlastnosti Items komponenty RadioGroup a vytvořte dvě položky: Area a Population.

6. Ošetřete událost OnClick tlačítka Button1:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Series1.DataSource := Table1;
  Series1.YValues.ValueSource := `Area` ;
  RadioGroup1.ItemIndex := 0;
end;

Tento kód zasluhuje bližší vysvětlení. Stejně jako u komponenty Chart, také u DBChart je nutné naplnit datové série za běhu programu. Vzhledem k provázanosti těchto komponent s databází však není náš úkol obtížný. Stejně jako u „obyčejného“ grafu Chart dojde po jeho vložení na formulář a vytvoření datové série k automatickému vložení deklarace Series1 do deklarace formuláře. Jednou z vlastností Series1 (která je podle zvoleného druhu grafu např. typu TAreaSeries) je DataSource, kterou naplníme komponentou datového zdroje (tedy DataSource1). Poslední vlastnost, kterou musíme naplnit, je YValues a její „podvlastnost“ ValueSource, která určuje, jaká položka datového zdroje se bude zobrazovat v grafu. Jakmile nastavíme tyto dvě vlastnosti, okamžitě v grafu můžeme kontrolovat hodnoty z tabulky country.db.

7. Další operace bude spočívat v ošetření události OnClick komponenty RadioGroup:

procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  Series1.YValues.ValueSource := RadioGroup1.Items[RadioGroup1.ItemIndex] ;
end;

Vysvětlení: po klepnutí na přepínací pole se změní hodnota ValueSource podle toho, jaký je momentálně aktivní přepínač komponenty RadioGroup. V grafu tak můžeme zobrazovat buď rozlohy zemí z tabulky country.db, nebo jejich populace.

8. Je samozřejmě jen a jen věcí vkusu, které další změny provedeme ve zdrojovém kódu a v editoru grafu. Možností je řada, počínaje nastavováním jiné legendy, popisků, titulku apod.

9. Už v době návrhu se ve všech komponentách zobrazují „živá“ data; překlad a spuštění aplikace je proto formalita:

Závěrem uveďme kompletní zdrojový kód této aplikace. Jako u mnoha jiných aplikací v Delphi, i u této je zdrojový kód nesmírně jednoduchý a krátký. Některá další nastavení je nutné provést v návrhové fázi, nicméně obecně lze říci, že vytvoření elegantně vypadající databázové aplikace, která je schopna data prezentovat grafickou formou, je otázkou několika málo minut.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Db, DBTables, Grids, DBGrids, TeEngine, Series, ExtCtrls, TeeProcs,
  Chart, DBChart, StdCtrls;

type
  TForm1 = class(TForm)
    DBChart1: TDBChart;
    DBGrid1: TDBGrid;
    DataSource1: TDataSource;
    Table1: TTable;
    Button1: TButton;
    Series1: TAreaSeries;
    RadioGroup1: TRadioGroup;
    procedure Button1Click(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Series1.DataSource := Table1;
  Series1.YValues.ValueSource := `Area` ;
  RadioGroup1.ItemIndex := 0;
end;

procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  Series1.YValues.ValueSource := RadioGroup1.Items[RadioGroup1.ItemIndex] ;
end;

end.

Na závěr

Grafy, které Delphi poskytují (a to jak „obyčejný“ Chart, tak i databázový DBChart), jsou velmi výkonné a umožňují širokou škálu voleb a možností nastavení. Jejich podrobný popis by vydal na samostatnou publikaci; věřím však, že i ze stručného popisu v rámci dnešního a minulého dílu seriálu jste okusili některé z mnoha možností, které je možné při požadavku na graf v Delphi využít.

Aplikace, jejichž výstupy jsou elegantní a grafické, vypadají profesionálněji a jsou pro uživatele příjemnější na používání. Pokud to s grafy nepřeženeme a zachováme určitou míru vkusu, je použití grafů vhodným nástrojem pro vytvoření elegantní aplikace.

Diskuze (3) Další článek: Zkušenosti: domácí WiFi a připojení více počítačů k internetu

Témata článku: , , , , , , , , , , , , , , , , , , ,