11. Třídy v C++ Builderu I
  1. Vraťme se k našemu prvnímu programu, který jsme v  vytvořili (formulář s tlačítkem měnícím barvu formuláře na zelenou). Zdrojový text tohoto programu je (hlavičkový a CPP soubor):

  2. #ifndef Unit1H
    #define Unit1H
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    class TForm1 : public TForm
    {
    __published:    // IDE-managed Components
     TButton *Button1;
     void __fastcall Button1Click(TObject *Sender);
    private:        // User declarations
    public:         // User declarations
     __fastcall TForm1(TComponent* Owner);
    };
    extern PACKAGE TForm1 *Form1;
    #endif

    #include <vcl.h>
    #pragma hdrstop
    #include "Unit1.h"
    #pragna package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    __fastcall TForm1::TForm1(TComponent* Owner)
     : TForm(Owner)
    {
    }
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
      Form1->Color = clGreen;
      Caption = "Zelené okno";
    }
    V uvedené jednotce je použita třída a objekt. V naší jednotce deklarujeme novou třídu TForm1, která je odvozena od třídy TForm. TForm1 je datový typ a pro práci v aplikaci potřebujeme proměnnou tohoto typu. Deklarujeme tedy instanci Form1 typu ukazatel na TForm1. Objekt Form1 reprezentuje samotný formulář (přesněji řečeno jedná se o ukazatel na formulář). Můžeme deklarovat více než jeden objekt nějaké třídy. Např. můžeme mít více podřízených oken v aplikací vícedokumentového rozhraní.
    Naše aplikace obsahuje tlačítko. Třída TForm1 má položku Button1, tj. přidané tlačítko. TButton je třída a tedy Button1 je objekt. Pokaždé, když vložíme novou komponentu na formulář, je vložena do deklarace typu formuláře nová položka se jménem komponenty. Všechny obsluhy událostí jsou metody třídy formuláře. Když vytvoříme novou obsluhu události, pak její metoda je také deklarována v typu formuláře. TForm1 obsahuje metodu Button1Click. Aktuální kód této metody je uveden v souboru CPP. Prohlédněte si jednotku některé jiné aplikace a zjistěte, které položky a metody jsou použity. Modifikátor __fastcall určuje způsob předávání parametrů u metod.
  3. Vytváření třídy v C++ Builderu začínáme odvozením třídy od existující třídy. Když přidáme do projektu nový formulář, pak C++ Builder jej automaticky odvozuje od TForm (je to definováno prvním řádkem deklarace typu třídy, v našem případě class TForm1 : public TForm). V okamžiku přidání formuláře do projektu je nová třída identická s typem TForm. Po přidání komponenty na formulář nebo po zápisu obsluhy události již identická není. Nový formulář je stále plně funkční formulář (můžeme měnit jeho velikost a umístění a můžeme jej také uzavřít). Nová třída formuláře totiž zdědila všechny datové položky, vlastnosti, metody a události od typu TForm. Třída od které odvozujeme svoji třídu (od které dědíme data a kód) se nazývá předek odvozené třídy. Odvozená třída je potomek svého předka. Prapředek všech tříd je třída TObject.
  4. Rozsah platnosti určuje použitelnost a přístupnost datových položek, vlastností a metod třídy; všechny jsou v rozsahu platnosti třídy a jsou použitelné třídou a jejími potomky. Když zapisujeme kód do obsluhy události třídy, který se odkazuje na vlastnost, metodu nebo položku třídy samotné, pak nemusíme uvádět v odkazu jméno objektu. Např. příkaz v obsluze události pro Form1 Form1->Color = clGreen; lze napsat jako Color = clGreen;. Rozsah platnosti třídy je rozšířen na všechny potomky třídy. Můžeme také použít jméno metody ze třídy předka k deklaraci metody ve třídě potomka. Jedná se o předefinování metody (metoda potom ve třídě potomka bude provádět něco jiného). Deklarace třídy obsahuje také klíčová slova private: a public: označující místa pro datové položky a metody, které chceme do kódu zapisovat přímo. Veřejnou část deklarace (část za klíčovým slovem public) používáme k deklarování datových položek a metod, ke kterým chceme přistupovat z jiných jednotek. K deklaracím v soukromé části (private) je omezen přístup pouze na tuto třídu.
  5. Nyní se pokusíme vytvořit vlastní třídu (jinou než třídu formuláře) a to třídu umožňující pracovat s datumem. Předpokládejme následující deklaraci:

  6. class TDatum : public TObject {
      int Den, Mesic, Rok;
    public:
      TDatum(){};
      void NastavHodnotu(int D, int M, int R);
      bool Prestupny();
    };
    Naše třída se skládá ze tří položek: Den, Mesic a Rok, bezparametrického konstruktoru (naši třídu odvozujeme od třídy, ve které je definován bezparametrický konstruktor a v odvozené třídě jej musíme tedy definovat také) a dvou metod: NastavHodnotu a Prestupny. Funkce NastavHodnotu může vypadat např. takto:
    void TDatum::NastavHodnotu(int D, int M, int R){
      Den = D;
      Mesic = M;
      Rok = R;
    }
    Nyní již můžeme deklarovat instanci třídy TDatum a s touto instancí pracovat.
    TDatum  *Datum;
    Touto deklarací jsme nevytvořili objekt, ale pouze místo pro uložení odkazu na objekt (ukazatel). Instance objektu vytváříme operátorem new. Následuje příklad práce s naší instancí:
    Datum = new TDatum;
    Datum->NastavHodnotu(27, 5, 1942);
    ?
    Naší třídu se pokusíme použít v nějaké aplikaci. Vytvoříme formulář se dvěmi tlačítky (přiřadíme jim texty 1996 a 1997), kterými budeme určovat rok a budeme zjišťovat, zda se jedná o přestupný rok. Vytvoření objektu Datum budeme provádět v obsluze události OnCreate formuláře (vytváření formuláře - tím zajistíme, že objekt je vytvořen před jeho použitím). Na závěr (v obsluze události OnDestroy formuláře) objekt opět zrušíme. Následuje výpis obou programových souborů našeho formuláře (hlavičkového souboru a souboru CPP):
    #ifndef Unit1H
    #define Unit1H
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    class TDatum {
      int Den, Mesic, Rok;
    public:
      TDatum(){};
      void NastavHodnotu(int D, int M, int R);
      bool Prestupny();
    };
    class TForm1 : public TForm
    {
    __published:    // IDE-managed Components
     TButton *Button1;
     TButton *Button2;
     void __fastcall FormCreate(TObject *Sender);
     void __fastcall Button1Click(TObject *Sender);
     void __fastcall Button2Click(TObject *Sender);
    private:     // User declarations
    public:     // User declarations
     __fastcall TForm1(TComponent* Owner);
    };
    TDatum *Datum;
    extern PACKAGE TForm1 *Form1;
    #endif


    #include <vcl\vcl.h>
    #pragma hdrstop
    #include "Unit1.h"
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    TDatim *Datum;
    __fastcall TForm1::TForm1(TComponent* Owner)
     : TForm(Owner)
    {
    }
    void TDatum::NastavHodnotu(int D, int M, int R){
      Den = D;
      Mesic = M;
      Rok = R;
    }
    bool TDatum::Prestupny(){
      if (Rok % 4 != 0) return false;
      else if (Rok % 100 != 0) return true;
        else if (Rok % 400 != 0) return false;
          else return true;
    }
    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
      Datum = new TDatum;
    }
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
      delete Datum;
    }
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
      Datum->NastavHodnotu(1, 1, 1996);
      if (Datum->Prestupny()) Caption = "Přestupný";
      else Caption = "Nepřestupný";
    }
    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
      Datum->NastavHodnotu(1, 1, 1997);
      if (Datum->Prestupny()) Caption = "Přestupný";
      else Caption = "Nepřestupný";
    }
    Prostudujte si tento výpis a zjistěte, co naše aplikace provádí. Vyzkoušejte.
  7. Pokuste se nyní vynechat příkaz v obsluze události OnCreate formuláře (objekt Datum nebudeme vytvářet). Aplikaci znovu přeložíme a teprve při stisku některého tlačítka je signalizována chyba, která indikuje přístup k neplatnému ukazateli. Vyzkoušejte.
  8. V naši třídě používáme bezparametrický konstruktor. Bylo by ale výhodné, aby náš konstruktor zároveň provedl inicializaci datových položek třídy a to podobně jako metoda NastavHodnotu. Vytvoříme tento konstruktor a změníme také obsluhu události OnCreate formuláře. Bude nyní tvořena příkazem:

  9. Datum = new TDatum(1, 1, 1900);
    Vyzkoušejte.
  10. Definici naší třídy také můžeme umístit do samostatné jednotky a přidáme nové metody. Začneme s vývojem nové aplikace, formulář zatím necháme prázdný a zvolíme File | New Unit a dostaneme tento kód:

  11. #include <vcl.h>
    #pragma hdrstop
    #include "Unit2.h"
    #pragma package(smart_init)
    Jednotku přejmenujeme na Datumy a zapíšeme do ní konečnou verzi definice třídy TDatum. Dostaneme tedy tyto dva soubory (H a CPP):
    #ifndef DatumyH
    #define DatumyH
    class TDatum {
    public:
      TDatum(int D, int M, int R);
      void NastavHodnotu(int D, int M, int R);
      bool Prestupny();
      void Zvetsi();
      void Zmensi();
      void Pricti(int PocetDni);
      void Odecti(int PocetDni);
      AnsiString ZiskejText();
    protected:
      int Den, Mesic, Rok;
      int DniVMesici();
    };
    #endif


    #include <vcl.h>
    #pragma hdrstop
    #include "Datumy.h"
    #pragma package(smart_init)
    TDatum::TDatum(int D, int M, int R){
      Den = D;
      Mesic = M;
      Rok = R;
    }
    void TDatum::NastavHodnotu(int D, int M, int R){
      Den = D;
      Mesic = M;
      Rok = R;
    }
    bool TDatum::Prestupny(){
      if (Rok % 4 != 0) return false;
      else
        if (Rok % 100 != 0) return true;
        else
          if (Rok % 400 != 0) return false;
          else return true;
    }
    int TDatum::DniVMesici(){
      switch (Mesic) {
        case 1:  case 3:  case 5:  case 7:  case 8:  case 10:
        case 12: return 31;
        case 4:  case 6:  case 9:
        case 11: return 30;
        case 2: if (Prestupny()) return 29;
                else return 28;
      };
    }
    void TDatum::Zvetsi(){
      if (Den < DniVMesici()) Den++;
      else if (Mesic < 12) { Mesic++; Den = 1; }
           else { Rok++; Mesic = 1; Den = 1; }
    }
    void TDatum::Zmensi(){
      if (Den > 1) Den--;
      else if (Mesic > 1) { Mesic--; Den = DniVMesici(); }
           else { Rok--; Mesic = 12; Den = DniVMesici(); }
    }
    void TDatum::Pricti(int PocetDni) {
      for (int N = 1; N <= PocetDni; N++) Zvetsi();
    }
    void TDatum::Odecti(int PocetDni) {
      for (int N = 1; N <= PocetDni; N++) Zmensi();
    }
    AnsiString TDatum::ZiskejText() {
      char pom[30];
      sprintf(pom, "%d.%d.%d", Den, Mesic, Rok);
      return AnsiString(pom);
    }
    Abychom tuto jednotku mohli vyzkoušet vložíme na již vytvořený formulář komponentu Label (zvětšíme velikost písma) a čtyři tlačítka (vybavíme je texty Další, Předchozí, Za 10 a Před 10). Objekt třídy TDatum vložíme jako soukromou položku do třídy formuláře. Vytvoříme obsluhy několika událostí a dostaneme:
    #ifndef Unit1H
    #define Unit1H
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    class TForm1 : public TForm
    {
    __published:   // IDE-managed Components
     TLabel *Label1;
     TButton *Button1;
     TButton *Button2;
     TButton *Button3;
     TButton *Button4;
     void __fastcall FormCreate(TObject *Sender);
     void __fastcall Button1Click(TObject *Sender);
     void __fastcall Button2Click(TObject *Sender);
     void __fastcall Button3Click(TObject *Sender);
     void __fastcall Button4Click(TObject *Sender);
    private:    // User declarations
       TDatum *Datum;
    public:    // User declarations
     __fastcall TForm1(TComponent* Owner);
    };
    extern PACKAGE TForm1 *Form1;
    #endif


    #include <vcl.h>
    #pragma hdrstop
    #include "Datumy.h"
    #include "Unit1.h"
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    __fastcall TForm1::TForm1(TComponent* Owner)
     : TForm(Owner)
    {
    }
    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
      Datum = new TDatum(14, 2, 1995);
      Label1->Caption = Datum->ZiskejText();
    }
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
      Datum->Zvetsi();
      Label1->Caption = Datum->ZiskejText();
    }
    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
      Datum->Zmensi();
      Label1->Caption = Datum->ZiskejText();
    }
    void __fastcall TForm1::Button3Click(TObject *Sender)
    {
      Datum->Pricti(10);
      Label1->Caption = Datum->ZiskejText();
    }
    void __fastcall TForm1::Button4Click(TObject *Sender)
    {
      Datum->Odecti(10);
      Label1->Caption = Datum->ZiskejText();
    }
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
      delete Datum;
    }
    Prostudujte si text této aplikace a pokuste se pochopit jak pracuje. Aplikaci vyzkoušejte.
  12. Jako jednoduchý příklad dědičnosti můžeme pozměnit předchozí aplikaci odvozením nové třídy a modifikováním jedné z jeho funkcí. Měsíc v datumu budeme vypisovat slovně, a změníme tedy metodu ZiskejText. Vytvoříme další třídu (zapíšeme ji do jednotky Datumy):

  13. class TNoveDatum : public TDatum {
    public:
      TNoveDatum(int D, int M, int R): TDatum(D, M, R){};
      AnsiString ZiskejText();
    };
    Nová funkce ZiskejText používá k výpisu data konstantní pole s názvy měsíců:
    char JmenaMesicu[12][10] =
      {"leden", "únor", "březen", "duben", "květen", "červen",
       "červenec", "srpen", "září", "říjen", "listopad", "prosinec"};
    AnsiString TNoveDatum::ZiskejText() {
      char pom[30];
      sprintf(pom, "%d. %s %d", Den, JmenaMesicu[Mesic-1], Rok);
      return AnsiString(pom);
    }
    V naší aplikaci musíme ještě změnit TDatum na TNoveDatum (v deklaraci formuláře a v obsluze OnCreate) a aplikaci můžeme vyzkoušet.
  14. VCL je dobře navržený pracovní rámec. Je zde v maximální možné míře použita dědičnost. Jádrem VCL je třída reprezentující komponentu. Na následujícím obrázku je uvedena hierarchie tříd VCL. Není to úplné schéma hierarchie, ale pouze nepatrná část. Na vrcholu nalezneme TObject. TObject je prapředek všech tříd VCL. Pod TObject vidíme TPersistent. Tato třída dává komponentám možnost uložit se do souboru a do paměti a další detaily nepotřebujeme znát.
  15. TComponent slouží jako základní třída pro komponenty. Tato třída poskytuje všechnu funkčnost, kterou společný základ komponent vyžaduje. Nevizuální komponenty jsou odvozeny přímo od TComponent. Vizuální komponenty jsou odvozeny od třídy TControl, která je odvozena od TComtonent. TControl přidává další funkčnost, kterou vyžadují vizuální komponenty. Jednotlivé komponenty jsou pak odvozeny od TGraphicControl nebo TWinControl.
  16. Třídy Form a Application reprezentují objekty formulářů a aplikace ve VCL. Tyto třídy jsou odvozeny od TComponent a jsou tedy také komponentami.

  17. Třída TApplication zaobaluje základní operace Windowsovského programu. TApplication udržuje ikonu aplikace, poskytuje kontext nápovědy a provádí základní zpracování zpráv. Každá aplikace C++ Builderu má ukazatel na objekt TApplication nazvaný Application. Některé vlastnosti tohoto objektu můžeme nastavovat na stránce Application dialogového okna Project Options (zobrazí se volbou Project | Options). Třída TForm zaobaluje ve VCL formuláře. Formuláře jsou použity jako různá okna aplikace.
     
11. Třídy v C++ Builderu I