21. Různé aplikace I
  1. V další aplikaci se budeme zabývat prací se soubory. Z existujícího textového souboru se pokusíme vytvořit nový soubor s pozměněným obsahem. Program nazvaný FILTR umí převést všechny znaky textového souboru na velká písmena, zvětšit jen písmena každého prvního slova ve větě nebo odstranit znaky z druhé části tabulky ASCII (písmena s háčky a čárkami). Vytvoříme formulář podle následujícího obrázku (je zde použita komponenta RadioGroup):


  2. Uživatel může zadat názvy vstupního a výstupního souboru ve dvou editačních ovladačích nebo stisknutím tlačítka Změnit otevřít příslušné dialogové okno pro výběr souboru. U komponenty OpenDialog nastavíme Options na [ofPathMustExist, ofFileMustExist] a u SaveDialog na [ofOverwritePrompt, ofPathMustExist, ofCreatePrompt]. Vlastnosti Filter u obou dialogových oken nastavíme na Textový soubor (*.txt). Obsluha stisku horního tlačítka Změnit bude tvořena příkazem (pro spodní tlačítko ji vytvořte sami):
    if (OpenDialog1->Execute()) Edit1->Text = OpenDialog1->FileName;
    Do soukromé části deklarace formuláře vložíme tyto deklarace:
    FILE *VstSoubor, *VystSoubor;
    int DelkaSouboru;
    void __fastcall PrevodVelka();
    void __fastcall PrevodPrvniVelk();
    void __fastcall PrevodSymb();
    Obsluha stisku tlačítka Převést je tvořena příkazy (je zobrazeno dialogové okno, které bude popisovat proces převodu; tento další formulář bude obsahovat pouze komponentu ProgressBar a BitBtn s textem OK; tlačítko zakážeme a obsluha jeho stisknutí bude tvořena příkazem Close();):
    if ((Edit1->Text != "")&&(Edit2->Text != "")) {
      if ((VstSoubor = fopen(Edit1->Text.c_str(), "rt")) == NULL){
        ShowMessage("Nelze otevřít vstupní soubor.");
        return;
      }
      fseek(VstSoubor, 0L, SEEK_END);
      DelkaSouboru = ftell(VstSoubor);
      fseek(VstSoubor, 0L, SEEK_SET);
      if ((VystSoubor = fopen(Edit2->Text.c_str(), "wt")) == NULL){
        ShowMessage("Nelze otevřít výstupní soubor.");
        return;
      }
      Form2->Show();
      Form2->BitBtn1->Enabled = false;
      Button3->Enabled = false;
      switch (RadioGroup1->ItemIndex) {
        case 0: PrevodVelka(); break;
        case 1: PrevodPrvniVelk(); break;
        case 2: PrevodSymb(); break;
      }
      fclose(VstSoubor);
      fclose(VystSoubor);
      Form2->BitBtn1->Enabled = true;
      Button3->Enabled = true;
    }
    else ShowMessage("Zadej jména souborů.");
    Zbývá ještě vytvořit tři metody provádějící požadovaný převod. Následuje výpis těchto metod (je zde i pomocná funkce převádějící velká písmena na malá):
    char __fastcall LowCase(char Zn)
    {
      if ((Zn >= 'A') && (Zn <= 'Z')){return Zn - 'A' + 'a';}
      else return Zn;
    }
    void __fastcall TForm1::PrevodVelka()
    {
      int Pozice = 0;
      while (!feof(VstSoubor)) {
        fputc(UpCase(fgetc(VstSoubor)), VystSoubor);
        Pozice++;
        Form2->ProgressBar1->Position = Pozice * 100 / DelkaSouboru;
        Application->ProcessMessages();
      }
    }
    void __fastcall TForm1::PrevodPrvniVelk()
    {
      int Pozice = 0;
      char Zn;
      bool Tecka = true;
      while (!feof(VstSoubor)) {
        Zn = fgetc(VstSoubor);
        if ((Zn >= 'A') && (Zn <= 'Z')){
          if (Tecka) {fputc(Zn, VystSoubor); Tecka = false;}
          else {fputc(LowCase(Zn), VystSoubor); Tecka = false;}
        }
        else if ((Zn >= 'a') && (Zn <= 'z')){
          if (Tecka) {fputc(UpCase(Zn), VystSoubor); Tecka = false;}
          else {fputc(Zn, VystSoubor); Tecka = false;}
        }
        else if ((Zn == '.') || (Zn == '?') || (Zn == '!'))
          {fputc(Zn, VystSoubor); Tecka = true;}
        else {fputc(Zn, VystSoubor);}
        Pozice++;
        Form2->ProgressBar1->Position = Pozice * 100 / DelkaSouboru;
        Application->ProcessMessages();
      }
    }
    void __fastcall TForm1::PrevodSymb()
    {
      int Pozice = 0;
      int Zn;
      while (!feof(VstSoubor)) {
        Zn = fgetc(VstSoubor);
        if (Zn < 128) fputc(Zn, VystSoubor);
        Pozice++;
        Form2->ProgressBar1->Position = Pozice * 100 / DelkaSouboru;
        Application->ProcessMessages();
      }
    }
    Tím je naše aplikace hotova a můžeme ji vyzkoušet. Pokuste se pochopit jak pracuje. V aplikaci se pracuje se soubory způsobem používaným v C. Pokuste se tuto aplikaci změnit tak, aby byly použity souborové datové proudy.
  3. Vedle používání textových souborů můžeme také běžným způsobem používat binární soubory. Nejprve vytvoříme aplikaci, která bude zobrazovat grafy (seznámíme se s komponentou Chartfx) a později k této aplikaci přidáme možnost ukládat a načítat zobrazené hodnoty do a ze souborů. Začneme s vývojem nové aplikace. Formulář zvětšíme a umístíme na ní komponentu Chartfx (bude zabírat asi horní dvě třetiny formuláře). Na zbývající plochu formuláře umístíme ještě komponentu StringGrid (s pěti sloupci a čtyřmi řádky, bez fixních řádků a sloupců, bez posuvníků a k vlastnosti Options přidáme hodnotu goEditing), komponentu ComboBox (vlastnost Style nastavíme na csDropDownList a prvky seznamu nastavíme na 1 – Lines, 2 – Bar, 3 – Spline, 4 – Mark, 5 – Pie, 6 – Area, 7 – Pareto, 8 – Scatter a 9 – HiLow) a tlačítko s textem Aktualizuj. Mřížku řetězců můžeme editovat a aby přijímala pouze celá čísla musíme obsloužit její událost OnGetEditMask. Tuto obsluhu bude tvořit příkaz:

  4. Value = "!09";
    Tím máme možnost zadat do buněk mřížky jednomístné nebo dvojmístné číslo. Při vytváření formuláře vyplníme mřížku náhodnými hodnotami a voláním obsluhy stisku tlačítka Aktualizuj tyto hodnoty zobrazíme v grafu. Do veřejné části deklarace formuláře přidáme:
    bool Modifikovano;
    AnsiString AktualniSoubor;
    Obsluha OnActivate formuláře bude tedy tvořena příkazy:
    Randomize();
    for (int I = 0; I < 5; I++)
      for (int J = 0; J < 4; J++)
        StringGrid1->Cells[I][J] = IntToStr(random(100));
    Button1Click(this);
    ComboBox1->ItemIndex = Chartfx1->ChartType - 1;
    Modifikovano = true;
    AktualniSoubor = "";
    a obsluhu stisku tlačítka Aktualizuj tvoří příkazy (nápovědu k používání komponenty Chartfx vyvoláme stiskem F1 při vybrané komponentě; nápověda je ale určena pro Delphi):
    Chartfx1->OpenDataEx(COD_VALUES,5,4);
    for (int I = 0; I < 5; I++){
      Chartfx1->ThisSerie = I;
      for (int J = 0; J < 4; J++)
        Chartfx1->Value[J] = StrToIntDef(StringGrid1->Cells[I][J], 0);
    }
    Chartfx1->CloseData(COD_VALUES);
    Modifikovano = true;
    Zbývá ještě vytvořit obsluhu události OnChange kombinovaného ovladače. Tvoří ji příkazy:
    Chartfx1->ChartType = ComboBox1->ItemIndex + 1;
    Modifikovano = true;
    Tím je tato část aplikace hotova. Vyzkoušejte jak pracuje.
  5. Aplikaci vytvořenou v předchozím zadání rozšíříme o možnost ukládání zobrazovaných dat do souboru. K aplikaci přidáme nabídku Soubor s volbami Otevřít, Uložit a Uložit jako (můžete přidat i volbu Konec) a přidáme komponenty OpenDialog a SaveDialog (nastavíme vhodně jejich vlastnosti). Obsluha volby Otevřit bude tvořena příkazy:

  6. FILE *LoadFile;
    if (OpenDialog1->Execute()) {
      AktualniSoubor = OpenDialog1->FileName;
      Caption = "Graf " + AktualniSoubor;
      if ((LoadFile = fopen(AktualniSoubor.c_str(), "rb")) == NULL){
        ShowMessage("Nelze otevřít soubor");
        return;
      }
      int Hodnota;
      for (int I = 0; I < 5; I++)
        for (int J = 0; J < 4; J++){
          fread(&Hodnota, sizeof(Hodnota), 1, LoadFile);
          StringGrid1->Cells[I][J] = IntToStr(Hodnota);
        }
      fread(&Hodnota, sizeof(Hodnota), 1, LoadFile);
      ComboBox1->ItemIndex = Hodnota;
      Modifikovano = false;
      fclose(LoadFile);
      ComboBox1Change(this);
      Button1Click(this);
    }
    Zbývající obsluhy vytvořte sami a aplikaci vyzkoušejte. Nahraďte opět souborové operace C souborovými datovými proudy.

  7. V předchozí aplikaci jsme se seznámili s komponentou Chartfx. Nyní se touto komponentou budeme zabývat podrobněji.

  8. Graf vytváříme stejným způsobem, jako používáme jiné komponenty. Umístíme komponentu na formulář a nastavíme její inicializační vlastnosti.
    Po vytvoření grafu je nutno alespoň specifikovat data, která chceme zobrazit (zatím jsou zobrazována náhodná data). Pro specifikaci zobrazených dat musíme použít vlastnost Value, tj. vlastnost, která musí být použita jako pole vlastností:
    Chart1->Value[nPoint] = dValue;
    Tento příkaz říká, že hodnota dValue je hodnota bodu nPoint v sérii určené vlastností ThisSerie. Např. následující příkazy nastavují bod 3 série 0 na hodnotu 10.5:
    Chart1->ThisSerie = 0;
    Chart1->Value[3] = 10.5;
    Před použitím této vlastnosti je nutno zajistit, že komunikační kanál ke komponentě je otevřen. To provedeme dvojicí metod OpenDataEx a CloseData. Kód nastavující data tedy bude vypadat např. takto:
    // Otevření kanálu hodnot specifikujícího 2 série a 7 bodů
    Chart1->OpenDataEx(COD_VALUES,2, 7);
    // Kód nastavující data
    Chart1->ThisSerie = 0;
    for (int i = 0; i < 7; i++)
     Chart1->Value[i] = 9;
    Chart1->ThisSerie = 1;
    for (int i = 0; i < 7; i++)
     Chart1->Value[i] = 15;
    // Uzavření kanálu hodnot
    Chart1->CloseData(COD_VALUES);
    Tímto způsobem se nastavují zobrazovaná data. Můžeme si to nyní vyzkoušet. Začneme vývoj nové aplikace, na formulář umístíme komponentu ChartFX, změníme její jméno na Chart1, zvětšíme její velikost tak, aby zabírala většinu plochy formuláře a vytvoříme obsluhu události OnActivate formuláře s tímto kódem:
    Chart1->OpenDataEx(COD_VALUES,2,8);
    Chart1->ThisSerie = 0;
    Chart1->Value[0] = 30;
    Chart1->Value[1] = 20;
    Chart1->Value[2] = 40;
    Chart1->Value[3] = 60;
    Chart1->Value[4] = 50;
    Chart1->Value[5] = 15;
    Chart1->Value[6] = 24;
    Chart1->Value[7] = 35;
    Chart1->ThisSerie = 1;
    Chart1->Value[0] = 45;
    Chart1->Value[1] = 60;
    Chart1->Value[2] = 30;
    Chart1->Value[3] = 60;
    Chart1->Value[4] = 80;
    Chart1->Value[5] = 45;
    Chart1->Value[6] = 15;
    Chart1->Value[7] = 45;
    Chart1->CloseData(COD_VALUES);
    Po spuštění aplikace jsou zde zadaná data zobrazena v grafu. Vyzkoušejte.
  9. Nyní se budeme zabývat změnou dat zobrazených v grafu v předchozím kroku. Pro změnu zobrazených hodnot používáme stejné vlastnosti jako pro počáteční zadávání dat.

  10. Volání OpenDataEx s novým počtem sérií a bodů zruší existující data a připraví komunikační kanál k přijetí nových dat.
    // Otevírá kanál hodnot specifikací 4 sérií a 8 bodů
    // Toto volání zruší existující data
    Chart1->OpenDataEx(COD_VALUES,4, 8);
    // Kód nastavující data
    for (int i = 0; i < 4; i++){
     Chart1->ThisSerie = i;
     for (int j = 0; j < 8; j++){
      Chart1->Value[j] = 12;
     }
    }
    // Uzavření kanálu hodnot
    Chart1->CloseData(COD_VALUES);
    Pokud pouze chceme změnit hodnoty beze změny počtu bodů nebo sérií, pak můžeme použít příznak COD_UNCHANGE, který znamená, zachování starých dat s výjimkou těch, které změníme ve vlastnosti Value:
    // Otevírá kanál hodnot se zachováním počtu sérií a bodů
    Chart1->OpenDataEx(COD_VALUES, COD_UNCHANGE, COD_UNCHANGE);
    // Modifikace požadovaného bodu
    Chart1->ThisSerie = 1;
    Chart1->Value[4] = 10.5;
    // Uzavření kanálu hodnot
    Chart1->CloseData(COD_VALUES);
    Budeme pokračovat ve vývoji aplikace z předchozího bodu. Na formulář přidáme tlačítko a vytvoříme obsluhu jeho stisknutí kódem:
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
      int i;
      double d1[8],d2[8];
      randomize();
      for(i=0; i<7; i++){
        d1[i] = random(100);
        d2[i] = random(100);
      }
      NastavNoveHodnoty(d1, d2);
    }
    Dále vytvoříme soukromou metodu formuláře:
    void __fastcall TForm1::NastavNoveHodnoty(double *d1, double *d2)
    {
      int i;
      Chart1->OpenDataEx(COD_VALUES,2,8);
      Chart1->ThisSerie = 0;
      for (i=0; i<7; i++)
        Chart1->Value[i] = d1[i];
      Chart1->ThisSerie = 1;
      for (i=0; i<7; i++)
        Chart1->Value[i] = d2[i];
      Chart1->CloseData(COD_VALUES);
    }
    Nyní aplikaci můžeme opět vyzkoušet. Vždy při stisku tlačítka se změní zobrazené hodnoty.
  11. Dále se stručně seznámíme s titulky a legendami grafu. Ke změně titulků grafu nastavíme vlastnost Title s kódem titulku v indexu na nový řetězec titulku, jak je ukázáno dále:

  12. Chart1->Title[CHART_LEFTTIT] = "Vydaje";
    Chart1->Title[CHART_BOTTOMTIT] = "Mesice";
    Pro změnu legendy grafu použijeme vlastnost Legend pro jednotlivé body a vlastnost SerLeg pro jednotlivé série a kód zadávající legendu může vypadat takto:
    Chart1->Legend[0] = "Leden";
    Chart1->Legend[1] = "Unor";
    Chart1->Legend[2] = "Brezen";
    Chart1->Legend[3] = "Duben";
    Chart1->SerLeg[0] = "Prijmy";
    Chart1->SerLeg[1] = "Vydaje";
    Přejdeme opět k naši aplikaci. Přidání titulků a legendy provedeme přidáním následujícího kódu  na závěr obsluhy události OnActivate formuláře:
    Chart1->Title[CHART_LEFTTIT] = "Platby";
    Chart1->Title[CHART_BOTTOMTIT] = "Mesice";
    Chart1->Legend[0] = "Leden";
    Chart1->Legend[1] = "Únor";
    Chart1->Legend[2] = "Březen";
    Chart1->Legend[3] = "Duben";
    Chart1->Legend[4] = "Květen";
    Chart1->Legend[5] = "Červen";
    Chart1->Legend[6] = "Červene";
    Chart1->Legend[7] = "Srpen";
    Chart1->SerLeg[0] = "Prod A";
    Chart1->SerLeg[1] = "Prod B";
    Aplikaci vyzkoušejte.
  13. Dále se budeme zabývat změnou vzhledu (změnou vizuálních atributů) grafu. Komponenta poskytuje 3 režimy zobrazení:
  14. Implicitní režim vytváření grafu je režim 2D. Toto můžeme přepsat a nastavit jako implicitní režim "plochého" 3D při návrhu nebo za běhu aplikace použitím vlastnosti Chart3D takto:
    Chart1->Chart3D = true;
    K nastavení režimu prohlížení 3D, musí být graf v režimu "plochého" 3D a provedeme následující volání:
    Chart1->Angles3D = MAKELONG(30,60);
    Dále se budeme zabývat změnou některých atributů, které definují způsob zobrazení grafu. Všechny tyto atributy mohou být změněny koncovým uživatelem prostřednictvím nabídky a svázaných dialogů. V následujícím kódu jsou použity vlastnosti, které umožňují měnit zobrazovací atributy:
    // nastavení barevného schématu
    Chart1->Scheme = CHART_CSPATTERN;
    // nastavení rastru
    Chart1->Grid = CHART_HORZGRID;
    // nastavení barev pozadí
    Chart1->RGBBk = RGB(0,255,0);
    Chart1->RGB2DBk = RGB(200,20,90);
    Chart1->RGB3DBk = RGB(200,20,90);
    I když graf používá implicitně 8-mi bodové písmo Arial k zobrazení titulků, legend a dalších textů v grafu a programátor může měnit tato písma pomocí několika vlastností Font.
    Opět se vrátíme k naši aplikaci. Na konec obsluhy události OnActivite přidáme příkazy:
    Chart1->View3D = true;
    Chart1->Angles3D = MAKELONG(30,60);
    Chart1->Grid = CHART_HORZGRID;
    Chart1->RGBBk = RGB(0,255,0);
    Chart1->RGB2DBk = RGB(200,20,90);
    Chart1->RGB3DBk = RGB(200,20,90);
    Vyzkoušejte.
  15. Dále se budeme zabývat vytvářením nástrojů a dalších vizuálních prvků v grafech. Graf dává koncovému uživateli 4 nástroje, které usnadňují změnu některých charakteristik grafu.

  16.  
    Nástroj (vlastnost) Použit k ...
    ToolBar Změna typu grafu, rastru apod.
    LegendBar Zobrazuje legendu X a sérií.
    PaletteBar Mění barvy pomocí techniky tažení.
    PatternBar Mění výplňové vzory pomocí techniky tažení.

    Z pohledu programátorů práce těchto nástrojů je plně transparentní a mohou být zobrazeny během návrhu nebo při běhu aplikace nastavením příslušné vlastnosti na true.
    TChartfx má zabudovaný kód k vytváření a zobrazování stavového řádku, k informování koncového uživatele o stavu programu. Nejsnadnějším způsobem vytváření stavového řádku je použití metod OpenDataEx a CloseData ve spojení s metodou SetStatusItem.
    Následuje příklad kódu vytvářejícího stavový řádek:
    // Otevření komunikačního kanálu
    Chart1->OpenDataEx(COD_STATUSITEMS,4,0);
    // Nastavení prvků
    Chart1->SetStatusItem(0,true,IDM_TEXT1,true,100,50,4,CHART_STLEFT);
    Chart1->SetStatusItem(1,true,IDM_TEXT2,true,80,80,5,CHART_STCENTER);
    Chart1->SetStatusItem(2,false,NULL,true,40,40,10,NULL);
    Chart1->SetStatusItem(3,true,IDM_TEXT3,true,50,30,2,CHART_STRIGHT);
    // Uzavření kanálu prvků
    Chart1->CloseData(COD_STATUSITEMS);
    Kód nutný k zobrazení stavového řádku je:
    Chart1->ShowStatus = true;
    a kód k zadání textu prvku stavového řádku je:
    // wIdm je identifikace prvku použitá při jeho vytváření
    Chart1->StatusText[wIdm] = "Nový text";
    Použití těchto příkazů pro vytváření nástrojů a ovládání stavového řádku si ukážeme opět na naši aplikaci. Následující příkazy přidáme opět na závěr obsluhy událostí OnActivate:
    Chart1->OpenDataEx(COD_STATUSITEMS,4,0);
    Chart1->SetStatusItem(0, true, 101, true, 100, 50, 4, 0);
    Chart1->SetStatusItem(1, true, 102, false, 80, 80, 5, 0);
    Chart1->SetStatusItem(2, false, 0, true, 40, 40, 10, 0);
    Chart1->SetStatusItem(3, false, 103, true, 50, 30, 4, 0);
    Chart1->CloseData(COD_STATUSITEMS);
    Chart1->StatusText[101] = "Text 1";
    Chart1->StatusText[102] = "Text 2";
    Chart1->StatusText[103] = "Text 3";
    Chart1->ShowStatus = true;
    Chart1->ToolBar = true;
    Chart1->PaletteBar = true;
    Vyzkoušejte.

  17. Dále se budeme zabývat událostmi generovanými grafem a zpracováním těchto zpráv. Příklady některých událostí generovaných grafem jsou:

  18.  
    Událost Nastane když
    LButtonDblClk Uživatel dvojitě klikne na některou část grafu.
    RButtonDown Uživatel stiskne pravé tlačítko myši na grafem.
    Destroy Graf bude zrušen.
    ReadFile Má být čten soubor.
    Menu Uživatel zvolil uživatelem definovaný prvek nabídky.

    Obsluhy událostí se používají obvyklým způsobem. V naší aplikaci vytvoříme obsluhu dvojitého klinutí levým tlačítkem myši. Tato obsluha je tvořena příkazy:
    if ((nSerie == -1) || (nPoint == -1))
      ShowMessage("Prázdné dvojité kliknutí X: "+AnsiString(x)+" Y: " + y);
    else {
      Chart1->ThisSerie = nSerie;
      double Hodnota = Chart1->Value[nPoint];
      if (Hodnota < 25) nRes = 1;
      else
       if (Hodnota < 50) Chart1->HText = "Hodnota mezi 25 a 50";
    }
    Vyzkoušejte.
    Komponenta TChartfx umožňuje provádět ještě mnoho dalších činností. Nebudeme se jimi již ale zabývat.

21. Různé aplikace I