Vlastnosti jsou nejviditelnější částí komponent. Vývojář
aplikace je vidí a manipuluje s nimi při návrhu a získává bezprostřední
zpětnou vazbu reakcí komponenty na Návrhovém formuláři. Vlastnosti jsou
také důležité, neboť usnadňují používání komponent. Vlastnosti poskytují
významné výhody a to jak pro tvůrce komponent, tak i pro jejich uživatele.
Nejzřejmější výhodou je, že vlastnost může být v době návrhu zobrazena
v Inspektoru objektů. To zjednodušuje naše programování, neboť namísto
zadávání několika parametrů při vytváření objektu, zpracujeme hodnoty přiřazené
uživatelem.
K zajištění bezpečného použití vlastností v našich komponentách,
musíme pochopit:
Kdy vytvářet vlastnosti?
Pro uživatele komponent se vlastnosti podobají proměnným.
Uživatel může nastavovat nebo číst hodnoty vlastností, jako kdyby tyto
vlastnosti byly složkami tříd. Rozdíl je pouze v tom, že vlastnost nemůže
být použita jako parametr volaný odkazem.
Vlastnosti poskytují více možností než složky tříd neboť:
-
Uživatel může nastavovat vlastnosti během návrhu. To
je velmi důležité, neboť na rozdíl od metod, které jsou dostupné pouze
při běhu aplikace, vlastnosti slouží k přizpůsobení komponenty před spuštěním
aplikace. Vlastnosti mohou být zobrazeny v Inspektoru objektů, což zjednodušuje
programování (místo použití mnoha parametrů u konstruktoru třídy, C++ Builder
přečte hodnoty z Inspektora objektů). Inspektor objektů také zjišťuje přípustnost
použitých hodnot.
-
Na rozdíl od složek tříd, vlastnosti mohou skrýt implementační
detaily před uživateli. Např. data mohou být uložena v zakódovaném
tvaru, ale když nastavujeme nebo čteme hodnotu vlastnosti potřebujeme je
nezakódované. Přestože hodnota vlastnosti může být číslo, komponenta může
hledat hodnotu v databázi nebo k získání hodnoty provádět složité výpočty.
Vlastnosti poskytují k příkazu přiřazení vedlejší efekt. Když provedeme
přiřazení, je volána metoda, která může dělat cokoliv.
-
Implementace metody pro vlastnost může být virtuální,
což znamená, že může provádět různé věci v různých komponentách.
Příkladem je vlastnost Top všech komponent. Přiřazení
nové hodnoty vlastnosti Top, neznamená jenom změnu nějaké uložené
hodnoty, ale způsobí i přemístění a překreslení komponenty samotné. Efekt
nastavení vlastnosti není omezen na nějakou komponentu. Nastavení vlastnosti
Down
komponenty SpeedButton na true, způsobí nastavení
Down
všech ostatních tlačítek ve skupině na false.
Typy vlastností
Vlastnost může být libovolného typu. Důležitým aspektem volby
typu pro naši vlastnost je to, že různé typy jsou různě zobrazovány v Inspektoru
objektů. Inspektor objektů používá typ vlastnosti k určení co uživatel
chce zobrazit. Při registraci komponenty můžeme specifikovat různé editory
vlastností. V následující tabulce je uvedeno jak vlastnost je zobrazena
v Inspektoru objektů.
Typ vlastnosti |
Zacházení s vlastností v Inspektoru objektů |
Jednoduchý |
Číselné, znakové a řetězcové vlastnosti se zobrazují
v Inspektoru objektů jako čísla, znaky nebo řetězce. Uživatel může zadávat
a editovat hodnotu vlastnosti přímo. |
Výčtový |
Vlastnosti výčtových typů (včetně bool) zobrazují
hodnotu tak, jak je definována ve zdrojovém kódu. Uživatel může cyklicky
procházet možnými hodnotami dvojitým kliknutím ve sloupci hodnot. Hodnotu
můžeme také vybírat ze seznamu obsahujícího všechny možné hodnoty výčtového
typu. |
Množina |
Vlastnosti typu množina se zobrazují v Inspektoru objektů
jako množiny. Rozšířením množiny, uživatel může zacházet s každým prvkem
množiny jako s logickou hodnotou: true, jestliže prvek je obsažen
v množině nebo
false není-li. |
Objekt |
Vlastnost, která je sama objektem, často má svůj vlastní
editor vlastností. Nicméně, jestliže třída, která je vlastností má také
zveřejňované vlastnosti, pak Inspektor objektů umožňuje uživateli rozšířit
seznam vlastností objektu a editovat je samostatně. Tyto vlastnosti musí
být odvozeny od TPersistent. |
Pole |
Pole vlastností musí mít svůj vlastní editor vlastností.
Inspektor objektů nemá zabudovánu podporu pro editaci pole vlastností. |
Zveřejňování zděděných
vlastností
Všechny komponenty dědí vlastnosti od svých předků. Když
odvozujeme novou komponentu od existující komponenty, pak naše nová komponenta
dědí všechny vlastnosti ze třídy předka. Jestliže odvozujeme komponentu
od jednoho z abstraktních typů, pak zděděné vlastnosti jsou chráněné nebo
veřejné, ale ne zveřejňované.
Aby chráněná nebo veřejná vlastnost byla přístupná pro
uživatele komponenty v Inspektoru objektů, musíme ji opětovně deklarovat
jako zveřejňovanou. To provedeme přidáním deklarace zděděné vlastnosti
do deklarace třídy potomka.
Jestliže odvozujeme komponentu od TWinControl,
komponenta např. zdědí vlastnost Ctl3D, ale tato vlastnost je chráněná
a uživatel komponenty nemůže k Ctl3D během návrhu ani při běhu programu
přistupovat. Opětovnou deklarací Ctl3D v naší nové komponentě, můžeme
změnit úroveň ochrany na veřejnou nebo zveřejňovanou. Následující kód ukazuje
opětnou deklaraci
Ctl3D jako zveřejňovanou, což ji zpřístupní během
návrhu:
class PACKAGE TPrikladKomponenty : public
TWinControl
{
__published:
__property Ctl3D;
}
Opětovnou deklarací můžeme pouze zmírnit omezení přístupu
a nelze je zvětšit. Chráněnou vlastnost lze změnit na veřejnou, ale nelze
změnit veřejnou vlastnost na chráněnou. Při opakované deklaraci, specifikujeme
pouze jméno vlastnosti (typ a další informace se neuvádí). Můžeme také
deklarovat novou implicitní hodnotu nebo specifikovat, zda vlastnost ukládat.
Definování vlastnosti komponenty
V této části je uvedeno jak deklarovat nové vlastnosti a
jsou zde uvedeny některé konvence používané ve standardních komponentách.
Jsou zde body:
Deklarace vlastnosti
Deklarace vlastnosti a její implementace je snadná. Přidáme
deklaraci vlastnosti k deklaraci třídy naší komponenty. V deklaraci vlastnosti
specifikujeme tři věci: jméno vlastnosti, typ vlastnosti a metody pro čtení
a nastavování hodnot vlastnosti.
Vlastnosti komponent minimálně musíme deklarovat ve veřejné
části deklarace typu objektu komponenty, což umožní číst a nastavovat vlastnosti
z vnějšku komponenty při běhu programu. K vytvoření editovatelných komponent
během návrhu musíme deklarovat vlastnost ve zveřejňované části deklarace
třídy komponenty. Zveřejňované vlastnosti jsou automaticky zobrazovány
v Inspektoru objektů. Veřejné vlastnosti jsou přístupné pouze za běhu programu.
Následuje typická deklarace vlastnosti Pocet:
class PACKAGE TNaseKomponenta : public TComponent
{
private:
int FPocet;
// složka pro uložení vlastnosti
int __fastcall GetPocet();
// čtecí metoda
void __fastcall SetPocet(int APocet);
// zápisová metoda
public:
__property int Pocet={read=GetPocet,write=SetPocet}
//deklarace vlastnosti
}
Interní uložení dat (vlastností)
Není žádné omezení na to jak ukládat data vlastnosti. Komponenty
C++ Builderu ale používají tyto konvence:
-
Data vlastnosti jsou ukládány ve složkách třídy.
-
Složku třídy pro uložení vlastnosti deklarujeme jako soukromou.
To zajistí, že komponenta, která deklaruje vlastnost, má k ní přístup,
ale uživatelé komponenty a potomci komponenty ne. Potomci komponenty mohou
používat samotnou zděděnou vlastnost, ale nemají přístup k vnitřnímu uložení
dat komponenty.
-
Identifikátor pro složku vlastnosti třídy začíná písmenem
F
následované jménem vlastnosti. Např. data pro vlastnost Width jsou
uložena v položce objektu nazvané FWidth.
Základním principem těchto konvencí je, že pouze přístupové
metody vlastnosti mohou přistupovat k datům této vlastnosti. Jestliže metoda
nebo jiná vlastnost potřebuje změnit tato data, musí to provést prostřednictvím
vlastnosti a ne přímým přístupem k uloženým datům. To zajišťuje, že implementaci
zděděné metody můžeme měnit bez vlivu na potomky komponenty.
Přímý přístup
Nejjednodušší možností zpřístupnění dat je přímý přístup.
Tj. části
read a write deklarace vlastnosti specifikují,
že přiřazení nebo čtení hodnoty vlastnosti probíhá přímo se složkou vnitřního
uložení bez volání přístupové metody. Přímý přístup je užitečný, když vlastnost
nemá vedlejší efekty a chceme ji zpřístupnit v Inspektoru objektů. Často
používáme přímý přístup pro část read deklarace vlastnosti a přístupovou
metodu pro část write neboť obvykle aktualizujeme stav komponenty
na základě nové hodnoty vlastnosti.
Následující deklarace typu komponenty ukazuje vlastnost,
která používá přímý přístup pro čtení i zápis:
class PACKAGE TPrikladKomponenty : public
TComponent
{
private:
bool FPouzeProCteni; //vnitřní uložení
dat je soukromé
__published:
//umožňuje přístup k vlastnosti během návrhu
__property bool PouzeProCteni={read=FPouzeProCteni,write=FPouzeProCteni};
}
Přístupové metody
Syntaxe deklarace vlastnosti umožňuje, aby části read
a write deklarace vlastnosti specifikovaly přístupové metody místo
složky třídy. Přístupové metody musí být soukromé a jsou obvykle deklarované
jako virtuální. To umožňuje potomkům přepisovat implementaci a uživatelé
komponent nemohou tyto metody volat k modifikaci vlastnosti.
Přístupové metody by neměly být veřejné. Tím zabraňujeme
vývojáři aplikace v nedovolené modifikaci vlastnosti voláním některé z
těchto metod.
Následuje třída, která deklaruje tři vlastnosti používající
indexový specifikátor, což umožňuje aby všechny tři vlastnosti měly stejné
čtecí a zápisové metody.
class PACKAGE TPrikladKalendar : public TCustomGrid
{
private:
int __fastcall GetDatumovyPrvek(int
Index);
void __fastcall SetDatumovyPrvek(int
Index, int Hodnota);
public:
__property int Den = {read=GetDatumovyPrvek,
write=SetDatumovyPrvek, index=3, nodefault};
__property int Mesic = {read=GetDatumovyPrvek,
write=SetDatumovyPrvek, index=2, nodefault};
__property int Rok = {read=GetDatumovyPrvek,
write=SetDatumovyPrvek, index=1, nodefault};
};
Protože každý prvek datumu (den, měsíc a rok) je typu
int
a protože nastavování každého vyžaduje zakódování datumu při nastavení,
kód zabraňuje duplikacím sdílením čtecích a zápisových metod pro všechny
tři vlastnosti. Můžeme mít tedy pouze jednu metodu pro čtení datumového
prvku a další pro zápis datumového prvku.
Následuje čtecí metoda, která získává datumový prvek:
int __fastcall TPrikladKalendar::GetDatumovyPrvek(int
Index)
{
unsigned short ARok, AMesic, ADen;
int vysledek;
FDate.DecodeDate(&ARok, &AMesic,
&ADen); // rozložení datumu do prvků
switch (Index)
{
case 1: vysledek = ARok;
break;
case 2: vysledek = AMesic;
break;
case 3: vysledek = ADen;
break;
default: vysledek = -1;
}
return vysledek;
}
Toto je zápisová metoda, která nastavuje příslušný datumový
prvek:
void __fastcall TPrikladKalendar::SetDatumovyPrvek(int
Index, int Hodnota)
{
unsigned short ARok, AMesic, ADen;
if (Hodnota > 0)
// všechny prvky musí být kladné
{
FDate.DecodeDate(&ARok,
&AMesic, &ADen); // získání datumových prvků
switch (Index)
{
case 1: ARok
= Hodnota; break;
case 2: AMesic
= Hodnota; break;
case 3: ADen
= Hodnota; break;
default: return;
}
}
FDate = TDateTime(ARok, AMesic, ADen);
// zakódování modifikovaného datumu
Refresh();
// aktualizace viditelného kalendáře
}
Čtecí metoda pro vlastnost je funkce, která nemá parametry
a vrací hodnotu stejného typu jako má vlastnost. Podle konvencí, jméno
funkce začíná
Get a pokračuje jménem vlastnosti. Např. čtecí metoda
pro vlastnost nazvanou
Pocet by se měla jmenovat GetPocet.
Výjimkou je případ vlastnosti typu pole, která předává indexy jako parametry
čtecí metodě.
Čtecí metoda pracuje s vnitřním uložením dat a vytváří
hodnotu vlastnosti příslušného typu. Je-li vlastnost typu ?pouze pro zápis,
není nutné deklarovat čtecí metodu. Vlastnosti, určené pouze pro zápis se
používají velmi zřídka a obecně nejsou moc užitečnépis?,
Zápisová metoda pro vlastnost je vždy funkce typu void
s jedním parametrem, který je stejného typu jako vlastnost. Parametr může
být předáván odkazem nebo hodnotou a jeho jméno může být libovolné. Podle
konvencí jméno funkce je Set následované jménem vlastnosti. Např.
zápisová metoda pro vlastnost nazvanou Pocet by se měla jmenovat
SetPocet.
Hodnota předaná v parametru je použita k nastavení nové hodnoty vlastnosti
a zápisová metoda musí provést operace potřebné k vytvoření příslušné hodnoty
ve vnitřním formátu.Výjimkou k pravidlu jednoho parametru jsou vlastnosti
typu pole a vlastnosti používající specifikátor indexu (zde je předávána
hodnota indexu jako další parametr).
Je-li vlastnost typu pouze pro čtení, není nutné deklarovat
zápisovou metodu. Aby zveřejňované vlastnosti mohly být použity během návrhu
musí být definovány jako čtecí i zápisové.
Je vhodné testovat, zda se nová hodnota liší od současné
hodnoty před jejím přiřazením. Např. následuje příklad zápisové metody
pro vlastnost typu int nazvanou Pocet, která ukládá svou
aktuální hodnotu v položce FPocet:
void __fastcall TMojeKomponenta::SetPocet(int
Hodnota)
{
if (Hodnota != FPocet) {
FPocet = Hodnota;
Update();
}
}
Implicitní hodnota vlastnosti
Když deklarujeme vlastnost, můžeme pro ní volitelně deklarovat
implicitní hodnotu. Implicitní hodnota vlastnosti komponenty je hodnota
nastavená pro tuto vlastnost konstruktorem komponenty. C++ Builder používá
deklarovanou implicitní hodnotu k určení, zda ukládat vlastnost v souboru
formuláře. Jestliže implicitní hodnotu pro vlastnost nespecifikujeme, pak
C++ Builder vlastnost vždy ukládá. Např. když umístíme komponentu z Palety
komponent na formulář, pak C++ Builder vytváří komponentu voláním konstruktoru
komponenty, který určuje počáteční hodnoty vlastností komponenty.
K deklarování implicitní hodnoty pro vlastnost připojíme
klauzuli default k deklaraci (nebo opětovné deklaraci) následovanou
implicitní hodnotou. Např.
__property bool JePravda = {default=true};
Poznámka: Deklarací implicitní hodnoty v deklaraci
vlastnosti nenastavujeme aktuálně vlastnost na tuto hodnotu. Jako tvůrce
komponenty musíme zajistit, že konstruktor komponenty nastaví vlastnost
na tuto hodnotu. Jelikož objekty vždy inicializují své datové složky na
0, není nutno v konstruktoru nastavovat celočíselné hodnoty na 0, ukazatele
na NULL a logické vlastnosti na false.
Když opakovaně deklarujeme vlastnost, můžeme specifikovat,
že vlastnost nemá implicitní hodnotu, i když zděděná vlastnost ji má. K
určení, že vlastnost nemá implicitní hodnotu, připojíme klauzuli nodefault
k deklaraci vlastnosti. Např.
__property int NoveCislo = {nodefault};
Když deklarujeme vlastnost poprvé, není nutno specifikovat
nodefault,
protože absence deklarace implicitní hodnoty znamená totéž.
Následuje deklarace komponenty, která obsahuje vlastnost
JePravda
typu bool s implicitní hodnotou true a konstruktor nastavující
implicitní hodnotu:
class PACKAGE TPrikladKomponenty : public
TComponent
{
private:
bool FJePravda;
public:
virtual __fastcall TPrikladKomponenty(TComponent*
Owner);
__published:
__property bool JePravda = {read=FJePravfa,
write=FJePravda, default=true};
};
__fastcall TPrikladKomponenty::TPrikladKomponenty(TComponent
* Owner)
: TComponent(Owner)
{
FJePravda = true; //nastavení
implicitní hodnoty
}
Pokud by implicitní hodnota pro JePravda měla
být false, potom ji není nutno explicitně nastavovat v konstruktoru,
neboť všechny třídy (a tedy i komponenty) vždy inicializují všechny své
položky na nulu a ?nulová logická á? logická false.
Vytváření pole vlastností
Některé vlastnosti mohou být indexované, podobně jako pole.
Mají více hodnot, které rozlišujeme indexem. Příkladem ve standardních
komponentách je vlastnost Lines komponenty Memo. Lines
je indexovaný seznam řetězců, které tvoří text komponenty a můžeme k němu
přistupovat jako k poli řetězců. V tomto případě, vlastnost typu pole dává
uživateli přirozený přístup k jistému prvku (řetězci - řádku) ve větší
množině dat (textu komponenty).
Vlastnost typu pole pracuje stejně jako ostatní vlastnosti
a deklarujeme ji většinou stejně (jsou zde pouze tyto rozdíly): Deklarace
vlastnosti obsahuje jeden nebo více indexů určitého typu. Indexy mohou
být libovolného typu. Části read a write deklarace vlastnosti,
jsou-li specifikovány, pak musí být metodami. Nelze zde specifikovat položky
třídy.
Přístupové metody pro čtení a zápis hodnoty vlastnosti
přibírají další parametry, které odpovídají indexu nebo indexům. Parametry
musí být ve stejném pořadí a stejného typu jako indexy specifikované v
deklaraci vlastnosti. Na rozdíl od indexu pole, typ indexu pro vlastnost
typu pole nemusí být celočíselného typu. Např. jako index vlastnosti typu
pole může být i řetězec. Můžeme se odkazovat pouze na jednotlivé prvky
pole a ne na celé pole.
Následuje deklarace vlastnosti, která vrací na základě
celočíselného indexu řetězec:
Class PACKAGE TPrikladKomponenty : public
TComponent
{
private:
System::AnsiString __fastcall GetJmenoCisla(int
Index);
public:
__property System::AnsiString JmenoCisla[int
Index] = {read=GetJmenoCisla};
};
Toto je metoda GetJmenoCisla v CPP souboru:
System::AnsiString __fastcall TPrikladKomponenty::GetJmenoCisla(int
Index)
{
System::AnsiString Vysledek;
switch (Index){
case 0:
Vysledek =
"Nula";
break;
case 100:
Vysledek =
"Malé";
break;
case 1000:
Vysledek =
"Velké";
break;
default Vysledek = "Neznámé";
}
return Vysledek;
}
Ukládání a zavádění vlastností
C++ Builder ukládá formuláře a jejich komponenty v DFM souborech.
Tyto soubory jsou binární reprezentací vlastností formuláře a jeho komponent.
Komponenta přidaná na formulář je zapsána do formuláře a tedy komponenta
musí mít schopnost zapisovat své vlastnosti do souboru formuláře při uložení.
Obdobně, při zavádění ze souboru formuláře se komponenta musí sama obnovit.
Schopnost ukládat a obnovovat svou reprezentaci je zděděné chování komponenty.
Někdy ale můžeme potřebovat v tomto mechanismu provést nějakou změnu a
potřebuje se tedy s ním seznámit. Následují body:
Používání
ukládacího a zavádějícího mechanismu
Popis formuláře obsahuje seznam vlastností formuláře společně
s popisem všech komponent vložených na formulář. Každá komponenta včetně
samotného formuláře zodpovídá za ukládání a zavádění svého vlastního popisu.
Implicitně (při ukládání sama sebe) komponenta zapisuje
hodnoty všech svých veřejných a zveřejňovaných vlastností, které se liší
od svých implicitních hodnot a to v pořadí jejich deklarace. Při zavádění,
komponenta nejprve vytvoří sama sebe, nastaví všechny vlastnosti na jejich
implicitní hodnoty a potom čte neimplicitní uložené hodnoty vlastností.
Tento mechanismus vyhovuje většině komponent a nevyžaduje
žádnou akci od tvůrce komponenty. Je ale několik způsobů jak přizpůsobit
ukládací a zaváděcí proces potřebný pro některé komponenty.
Specifikování implicitních
hodnot
Komponenty ukládají své vlastnosti pouze tehdy, když se jejich
hodnoty liší od implicitních hodnot. Pokud nespecifikujeme jinak, pak C++
Builder předpokládá, že vlastnost nemá implicitní hodnotu, což znamená,
že komponenta vlastnost ukládá.
Vlastnost jejíž hodnota není nastavena konstruktorem
má nulovou hodnotu. Nulová hodnota znamená, ze oblast paměti rezervovaná
pro vlastnost je vynulována. Pokud potřebujeme něco jiného, pak implicitní
hodnotu specifikujeme explicitně. Např.
__property Alignment = {default=taCenter};
Implicitní hodnotu lze také specifikovat při opětovné
deklaraci vlastnosti. Významem opětovné deklarace vlastnosti je určit jinou
implicitní hodnotu.
Poznámka: Nezapomeňte, že specifikací implicitní
hodnoty není automaticky přiřazena tato hodnota vlastnosti při vytváření
objektu. Přiřazení hodnoty je nutno provést v konstruktoru komponenty.
Následující kód ukazuje deklaraci komponenty, která specifikuje
implicitní hodnotu pro vlastnost Align a implementaci konstruktoru
komponenty, který nastavuje implicitní hodnotu. V našem případě, nová komponenta
je speciální případ standardního panelu, který je použit jako stavový řádek
v okně (implicitně je umístěn u spodního okraje svého vlastníka).
class PACKAGE TMujStavovyRadek : public TPanel
{
public:
virtual __fastcall TMujStavovyRadek(TComponent*
AOwner);
__published:
__property Align = {default=alBottom};
};
Konstruktor TMujStavovyRadek v CPP souboru:
__fastcall TMujStavovyRadek::TMujStavovyRadek
(TComponent* AOwner)
: TPanel(AOwner)
{
Align = alBottom;
}
Určování co ukládat
Můžeme určovat, zda C++ Builder ukládá všechny vlastnosti
komponenty. Implicitně všechny vlastnosti ze zveřejňované části deklarace
třídy jsou ukládány. Můžeme zvolit, že daná vlastnost nebude ukládána nebo
můžeme navrhnout funkci, která bude určovat za běhu, zda vlastnost ukládat.
K určení zda ukládat vlastnost používáme specifikátor
stored
následovaný znakem = a true, false nebo jménem metody vracející
logickou hodnotu.
Následující kód ukazuje komponentu, která deklaruje tři
nové vlastnosti. Jedna je vždy ukládána, druhá není nikdy ukládána a třetí
je ukládána na základě hodnoty metody Ulozit:
class PACKAGE TPrikladKomponenty : public
TComponent
{
protected:
bool __fastcall Ulozit();
public:
__property int Ukladana = {stored=true};
// vždy ukládána
...
__published:
__property int Neukladana = {stored=false};
// nikdy ukládána
__property int Nekdy = {stored=Ulozit};
// uložení závisí na hodnotě funkce
};
Inicializace po zavedení
Po přečtení všech hodnot vlastností komponenty z jejího uloženého
popisu, je volána virtuální metoda nazvaná Loaded, která provádí
požadované inicializace. Volání Loaded nastane dříve než formulář
a jeho ovladače jsou zobrazeny a tedy inicializace nezpůsobí blikání na
obrazovce.
K inicializaci komponenty po zavedení jejich hodnot vlastností,
přepíšeme metodu Loaded.
Poznámka: První věcí, kterou v této metodě
musíme provést je volat zděděnou metodu Loaded. Tím zajistíme, že
zděděné vlastnosti jsou správně inicializovány před inicializací našich
vlastních vlastností.
-
Přejděme ke konkrétnímu příkladu. Budeme modifikovat standardní komponentu
Memo
k vytvoření komponenty, která implicitně neprovádí lámání slov a má žluté
pozadí. Je to velmi jednoduchý příklad, ale ukazuje vše, co je zapotřebí
provést k modifikaci existující komponenty. Implicitně hodnota vlastnosti
WordWrap
komponenty Memo je true. Jestliže používáme několik těchto
komponent u nichž nechceme provádět lámání slov (automatický přechod na
další řádek při dosažení konce řádku) můžeme snadno vytvořit novou komponentu,
která implicitně lámání slov neprovádí (implicitní hodnotu vlastnosti WordWrap
nastavíme na false). Modifikace existující komponenty probíhá ve
dvou krocích: vytvoření a registrace komponenty a modifikace objektu komponenty.
Základní proces je ale vždy stejný, ale u složitějších komponent budeme
muset provést více kroků pro přizpůsobení nové třídy. Vytváření každé komponenty
začíná stejně: vytvoříme programovou jednotku, odvodíme třídu komponenty,
registrujeme ji a instalujeme ji na Paletu komponent.
V našem příkladě použijeme uvedený obecný postup s těmito specifikami:
jednotku komponenty nazveme Memos, od TMemo odvodíme nový
typ komponenty nazvaný
TWrapMemo a registrujeme TWrapMemo
na stránce Samples Palety komponent. Výsledek naši práce je tento
(nejdříve je uveden výpis hlavičkového souboru):
#ifndef MemosH
#define MemosH
#include <vcl\sysutils.hpp>
#include <vcl\controls.hpp>
#include <vcl\classes.hpp>
#include <vcl\forms.hpp>
#include <vcl\StdCtrls.hpp>
class PACKAGE TWrapMemo : public TMemo
{
private:
protected:
public:
__published:
};
#endif
Následuje CPP soubor:
#include <vcl.h>
#pragma hdrstop
#include "Memos.h"
#pragma package(smart_init)
static inline TWrapMemo *ValidCtrCheck()
{
return new TWrapMemo(NULL);
}
namespace Memos
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1]
= {__classid(TWrapMemo)};
RegisterComponents("Samples",
classes, 0);
}
}
Poznámka: V tomto případě jsme nepoužili k vytvoření komponenty
Experta komponent, ale vytvořili jsme ji manuálně. Pokud bychom použili
Experta komponent, pak by byl do vytvořené třídy automaticky přidán konstruktor.
Všechny komponenty nastavují své hodnoty vlastností při vytváření.
Když umístíme komponentu během návrhu na formulář nebo když spustíme aplikaci
vytvářející komponentu a přečteme její vlastnosti ze souboru formuláře,
je nejprve volán konstruktor komponenty k nastavení implicitních hodnot
komponenty. V případě zavedení komponenty ze souboru formuláře, po vytvoření
objektu s jeho implicitními hodnotami vlastností, aplikace dále nastavuje
vlastnosti změněné při návrhu a když je komponenta zobrazena vidíme ji
tak, jak jsme ji navrhli. Konstruktor ale vždy určuje implicitní hodnoty
vlastností. Pro změnu implicitní hodnoty vlastnosti, předefinujeme konstruktor
komponenty k nastavení určené hodnoty. Když předefinujeme konstruktor,
pak nový konstruktor musí vždy volat zděděný konstruktor a to dříve než
provedeme cokoliv jiného.
V našem příkladě, naše nová komponenta musí předefinovat konstruktor
zděděný od TMemo k nastavení vlastnosti WordWrap na false
a vlastnosti Color na clYellow. Přidáme tedy deklaraci předefinovaného
konstruktoru do deklarace třídy a zapíšeme nový konstruktor do CPP souboru:
class PACKAGE TWrapMemo : public TMemo
{
public:
virtual __fastcall TWrapMemo(TComponent*
Owner);
};
__fastcall TWrapMemo::TWrapMemo(TComponent*
Owner)
: TMemo(Owner)
{
Color = clYellow;
WordWrap = false;
}
Poznámka: Pokud použijeme Průvodce komponentou k vytvoření
komponenty, pak vše co potřebujeme provést je přidání červeně označených
příkazů do konstruktoru.
Nyní můžeme instalovat novou komponentu na Paletu komponent a přidat
ji na formulář. Vlastnost WordWrap je nyní implicitně false
a vlastnost Color clYellow.
Jestliže změníme (nebo vytvoříme) novou implicitní hodnotu vlastnosti,
musíme také určit, že hodnota je implicitní. Jestliže to neprovedeme, Builder
nemůže ukládat a obnovovat hodnotu vlastnosti. Když C++ Builder ukládá
popis formuláře do souboru formuláře, ukládá pouze hodnoty vlastností,
které se liší od svých implicitních hodnot. Má to dvě výhody: zmenšuje
to soubor formuláře a urychluje zavádění formuláře. Jestliže vytvoříme
vlastnost nebo změníme implicitní hodnotu vlastnosti je vhodné aktualizovat
deklaraci vlastnosti na novou implicitní hodnotu. Ke změně implicitní hodnoty
vlastnosti, opětovně deklarujeme vlastnost a připojíme klauzuli default
s novou implicitní hodnotou. Není nutno opětovně deklarovat prvky vlastnosti,
pouze jméno a implicitní hodnotu. V našem příkladě provedeme:
class PACKAGE TWrapMemo : public TMemo
{
public:
virtual __fastcall TWrapMemo(TComponent*
Owner);
__published:
__property Color = {default=clYellow};
__property WordWrap = {default=false};
};
Specifikace implicitní hodnoty vlastnosti nemá vliv na celkovou práci
komponenty. Musíme stále explicitně nastavovat implicitní hodnotu v konstruktoru
komponenty. Rozdíl je ve vnitřní práci aplikace: Builder nezapisuje WordWrap
do souboru formuláře, jestliže je false, neboť předpokládá, že konstruktor
nastaví tuto hodnotu automaticky. Totéž platí i o vlastnosti Color.
-
Vytvořte komponentu FontCombo (kombinované okno volby písma). Tuto
komponentu odvoďte od komponenty ComboBox tak, že změníte implicitní
hodnotu vlastnosti Style na csDropDownList a do vlastnosti
Items
přiřadíte Screen->Fonts. Vyzkoušejte použití této komponenty v nějaké
aplikaci (bude signalizována chyba).
-
Namísto přiřazení seznamu písem v konstruktoru komponenty FontCombo
je nutno jej přiřadit v metodě Loaded, kterou předefinujeme (nejprve
voláme metodu předka a potom provedeme přiřazení). Vyzkoušejte provést
tuto změnu. Nyní již tuto komponentu můžeme používat bez problémů.
-
V dalším zadání se pokusíme modifikovat komponentu ListBox a to
tak, aby v zobrazeném textu mohly být použity znaky tabulátorů (ke stylu
okna je nutno přidat příznak LBS_USETABSTOPS). Komponentu nazveme
TabList
a předefinujeme v ní metodu CreateParams, kterou Builder používá
ke změně některých standardních hodnot používaných při tvorbě komponenty.
Tato metoda bude vypadat takto:
void __fastcall TTabBox::CreateParams(Controls::TCreateParams
&Params)
{
TListBox::CreateParams(Params);
Params.Style |= LBS_USETABSTOPS;
}
Komponentu vyzkoušejte.