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):
#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.
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.
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.
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:
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.
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.
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:
Datum = new TDatum(1,
1, 1900);
Vyzkoušejte.
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:
#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.
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):
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.
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.
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.
Třídy Form a Application reprezentují objekty
formulářů a aplikace ve VCL. Tyto třídy jsou odvozeny od TComponent
a jsou tedy také komponentami.
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.