Registrovanou komponentu instalujeme na Paletu komponent
a potom ji můžeme vybírat, umisťovat ji na formulář a manipulovat s ní
při návrhu. Udělání naší komponenty dostupné během návrhu vyžaduje několik
kroků:
Ne všechny tyto kroky vyžaduje každá komponenta. Např. jestliže
nedefinujeme žádnou novou vlastnost nebo událost, pak není nutné poskytnout
pro ní nápovědu. Vždy je pouze zapotřebí provést registraci a překlad.
Když již komponenty máme registrované a přeložené do
balíčků, pak je můžeme distribuovat ostatním vývojářům a instalovat v IDE.
Registrace komponenty
Registrace pracuje na základě jednotky a tedy, pokud vytváříme
několik komponent v jedné jednotce, pak je registrujeme všechny najednou.
Když již máme komponenty v jednotce registrovány, můžeme přeložit jednotku
do návrhového balíčku, který můžeme instalovat v IDE.
Pro registraci komponenty, přidáme funkci Register
do CPP souboru jednotky. V této funkci registrujeme komponenty a určíme,
kde na Paletě komponent budou instalovány.
Kroky pro ruční registraci komponenty jsou:
Deklarování funkce Register
Funkce Register musí existovat ve jmenném prostoru.
Jmenný prostor je jméno souboru komponent, kde s výjimkou prvního písmena
jsou všechna písmena malá.
Následující kostra kódu ukazuje jak funkce Register
je implementována ve jmenném prostoru. Jmenný prostor je nazván Novakomp,
zatímco soubor se jmenuje NovaKomp.CPP.
namespace Novakomp
{
void __fastcall PACKAGE Register()
{
}
}
Poznámka: Pokud
vytváříme naší komponentu volbou Component | New Component z hlavní
nabídky IDE, pak kód registrující naší komponentu je přidán automaticky.
Uvnitř funkce Register voláme RegisterComponents
pro všechny komponenty, které chceme přidat na Paletu komponent. Makro
PACKAGE je rozšířeno na příkazy, které umožňují importování a exportování
tříd.
Zápis funkce Register
Uvnitř funkce Register pro jednotku komponent, musíme
registrovat všechny komponenty, které chceme přidat na Paletu komponent.
Pokud jednotka obsahuje více komponent, pak je registrujeme všechny najednou.
K registraci komponent voláme funkci RegisterComponents
a
to jednou pro každou stránku Palety komponent, na kterou chceme přidávat
komponenty. Funkce RegisterComponents provádí dvě důležité věci:
specifikuje jména komponent a specifikuje stránku palety na kterou komponenty
instalujeme.
Uvnitř funkce Register deklarujeme otevřené pole
typu TComponentClass, které obsahuje informace o registrovaných
komponentách. Používáme tuto syntaxi:
TComponentClass classes[1] = {__classid(TNovaKomponenta)};
V tomto případě pole tříd obsahuje právě jednu komponentu,
ale do jednoho pole můžeme vložit všechny komponenty, které chceme registrovat.
Např. následující kód do pole umisťuje dvě komponenty:
TComponentClass classes[2] =
{__classid(TNovaKomponenta),
__classid(TJinaKomponenta)};
Jiným způsobem přidávání komponent do pole je přiřazování
komponent do pole v samostatných příkazech. Např. následující příkazy přidávají
do pole stejné dvě komponenty jako v předchozím příkladě:
TComponentClass classes[2];
classes[0] = __classid(TNovaKomponenta);
classes[1] = __classid(TJinaKomponenta);
Jméno stránky palety je typu AnsiString. Jestliže
stránka palety zadaného jména zatím neexistuje, pak C++ Builder vytváří
novou stránku s tímto jménem. C++ Builder hledá jména standardních stránek
ve zdroji seznamu řetězců, což umožňuje, aby internacionalizovaná verze
C++ Builderu mohla mít jména stránek ve svém přirozeném jazyku. Pokud chceme
instalovat komponentu na některou ze standardních stránek, pak musíme získat
řetězec pro jméno stránky voláním funkce LoadStr a předáme konstantu
reprezentující zdroj řetězce pro tuto stránku, např. srSystem pro
stránku System.
Uvnitř funkce Register voláme RegisterComponents
k registraci komponent v poli tříd. RegisterComponents je funkce
přebírající tři parametry: jméno stránky Palety komponent, pole tříd komponent
a index posledního prvku v poli. Následující funkce Register obsažená
v souboru NOVAKOMP.CPP, registruje komponentu nazvanou TMojeKomponenta
a umisťuje ji na stránku Palety komponent nazvanou "Různé".
namespace Novakomp
{
void __fastcall PACKAGE Register()
{
TComponentClass
classes[1] = {__classid(TMojeKomponenta)};
RegisterComponents("Různé",
classes, 0);
}
}
Povšimněte si, že třetí parametr ve volání RegisterComponents
je 0, což je index posledního prvku v poli tříd (velikost pole minus 1).
Můžeme také registrovat několik komponent na stejné stránce
najednou nebo registrovat komponenty na různých stránkách.
V následujícím příkladě jsou deklarované dvě pole: classes1
a classes2. V prvním volání RegisterComponents, classes1
má dva prvky a třetí parametr je index druhého prvku, což je 1. V druhém
volání RegisterComponents, classes2 má jeden prvek a třetí
parametr je 0.
namespace Mojekomp
{
void __fastcall PACKAGE Register()
{
// deklarace pole pro
dvě komponenty
TComponentClass classes1[2]
= {__classid(TPrvni), __classid(TDruha)};
// přidání nové palety
se dvěmi komponentami
RegisterComponents("Různé",
classes1, 1);
// deklarace druhého pole
TComponentClass classes2[1];
// přiřazení komponenty
prvnímu prvku v poli
classes2[0] = __classid(TTreti);
// přidání komponenty
na stránku Samples
RegisterComponents("Samples",
classes2, 0);
}
}
Přidání bitové mapy zobrazené na paletě
Každá komponenta vyžaduje bitovou mapu reprezentující komponentu
na Paletě komponent. Jestliže nespecifikujeme svou vlastní bitovou mapu,
pak C++ Builder použije implicitní. Jelikož bitové mapy palety jsou potřebné
pouze během návrhu, nejsou přeloženy v jednotce komponenty. Jsou v souboru
zdrojů Windows se stejným jménem jako má jednotka, ale s příponou DCR (Design-time
Component Resource). Tento soubor zdrojů můžeme vytvořit pomocí Editoru
obrázků v C++ Builderu. Každá bitová mapa je čtverec o straně 24 bodů.
Pro každou jednotku, kterou chceme instalovat, potřebujeme
soubor DCR a v každém souboru DCR potřebujeme bitovou mapu pro každou registrovanou
komponentu. Obraz bitové mapy má stejné jméno jako komponenta. Soubor DCR
musí být ve stejném adresáři jako jednotka komponenty, C++ Builder zde
tento soubor hledá, když instaluje komponenty na paletu.
Např. jestliže vytvoříme komponentu nazvanou TMujOvladac
v jednotce nazvané ToolBox, musíme vytvořit soubor zdrojů nazvaný
TOOLBOX.DCR,
který obsahuje bitovou mapu nazvanou TMUJOVLADAC. Ve jménech zdrojů
nezáleží na velikostí písmen, ale podle konvence je obvykle zapisujeme
velkými písmeny.
Poskytnutí nápovědy
pro naši komponentu
Když vybereme komponentu na formuláři, případně vlastnost
nebo událost v Inspektoru objektů, pak můžeme stisknutím F1 získat informaci
o tomto prvku. Uživatelé našich komponent mohou získat informace o naší
komponentě, jestliže vytvoříme příslušné soubory nápovědy.
Můžeme poskytnout malý soubor nápovědy s informacemi
o našich komponentách a uživatelé mohou nalézt naši dokumentaci bez nutnosti
nějakého speciálního kroku. Naše nápověda se stane částí nápovědného systému
C++ Builderu.
Pro poskytnutí kontextové nápovědy pro naši komponentu
provedeme následující kroky: Vytvoříme položky nápovědy, uděláme položky
kontextově senzitivní a přidáme nápovědu k nápovědě C++ Builderu.
K vytvoření souboru nápovědy můžeme použít libovolný
nástroj. C++ Builder obsahuje Microsoft Help Workshop, který můžeme použít
k vytvoření našeho nápovědného souboru.
Aby náš soubor nápovědy byl integrovatelný do nápovědy
knihovny komponent, je nutno dodržovat následující konvence:
-
Každá komponenta musí mít prvek nápovědy. Prvek nápovědy
komponenty obsahuje stručný popis komponenty. Prvek nápovědy komponenty
musí obsahovat vazby na sekundární okna, které popisují pozici komponenty
v objektové hierarchii a které obsahují seznamy všech vlastností, událostí
a metod dostupných pro uživatele komponenty. Vývojář aplikace zpřístupňuje
tento prvek nápovědy výběrem komponenty na formuláři a stiskem klávesy
F1. Prvek nápovědy musí mít poznámku pod čarou # s unikátní hodnotou pro
prvek. Poznámka pod čarou # jednoznačně identifikuje každý prvek v nápovědném
systému.
Prvek nápovědy komponenty musí mít poznámku pod čarou
"K"
pro vyhledávání klíčových slov, která obsahuje jméno třídy komponenty.
Např. poznámka pod čarou klíčového slova pro komponentu TMemo je
"TMemo".
Prvek nápovědy komponenty musí mít také poznámku pod čarou
"$",
která poskytuje titulek prvku. Titulek se zobrazuje v dialogovém okně Topics
Found, dialogovém okně Bookmark a okně History.
-
Každá komponenta musí mít sekundární navigační prvky nápovědy.
Jedná se o:
-
Prvek hierarchie s vazbami na každého předka komponenty v
hierarchii komponent.
-
Seznam všech vlastností dostupných v komponentě, s vazbami
na prvky popisující tyto vlastnosti.
-
Seznam všech událostí dostupných v komponentě, s vazbami
na prvky popisující tyto události.
-
Seznam metod dostupných v komponentě, s vazbami na prvky
popisující tyto metody.
Vazby na objekty tříd, vlastností, metod nebo událostí v
nápovědném systému C++ Builderu mohou být provedeny pomocí Alink.
Když se odkazujeme na objekt třídy, pak Alink používá jméno objektu
třídy, následované podtržením a řetězcem "object". Např. k vazbě
na objekt TCustomPanel použijeme:
!AL(TCustomPanel_object,1)
U vazeb na vlastnost, metodu nebo událost, předchází
jméno vlastnosti, metody nebo události jméno třídy, která ji implementuje
a podtržení. Např. k vazbě na vlastnost Text, která je implementovaná
TControl
použijeme:
!AL(TControl_Text,1)
Abychom viděli příklad sekundárních navigačních prvků,
zobrazíme prvek nápovědy nějaké komponenty a klikneme na vazbu popsanou
?hierarchy?,
?properties?,
?methods?
nebo ?events?.
-
Každá vlastnost, událost a metoda, která je deklarována
v komponentě musí mít nápovědný prvek. Prvek nápovědy vlastnosti, události
nebo metody zobrazuje deklaraci prvku a popisuje jeho používání. Vývojář
aplikace vidí tyto prvky po jejich výběru v Inspektoru objektů a stisku
F1 nebo umístěním textového kurzoru v Editoru kódu na jméno prvku a stiskem
F1.
Prvek nápovědy pro vlastnost, událost nebo metodu musí
obsahovat poznámku pod čarou "K" se jménem vlastnosti, metody nebo
události a toto jméno v kombinaci se jménem komponenty. Tedy vlastnost
TextTControl
má následující "K" poznámku pod čarou:
Text,TControl;TControl,Text;Text,
Prvek nápovědy vlastnosti, metody a události musí mít
také poznámku pod čarou "$", která určuje titulek prvku, např. ?TControl::Text?.
Všechny tyto prvky nápovědy musí mít unikátní ID prvku,
zadané jako poznámka pod čarou #.
Každý prvek nápovědy komponenty, vlastnosti, metody a události
musí mít poznámku pod čarou "A". Tato poznámka je použita k zobrazení
prvku, když uživatel vybere komponentu a stiskne F1 nebo když vlastnost
nebo událost je vybrána v Inspektoru objektů a uživatel stiskne F1. Poznámka
pod čarou "A" musí splňovat tyto konvence.
Pokud prvek nápovědy je pro třídu komponenty, pak poznámka
pod čarou obsahuje dvě položky oddělené středníkem při použití této syntaxe:
ComponentClass_Object;ComponentClass
kde ComponentClass je jméno třídy komponenty.
Pokud prvek nápovědy je pro vlastnost nebo událost, pak
poznámka pod čarou "A" obsahuje tři položky oddělené středníky při
použití této syntaxe:
ComponentClass_Element;Element_Type;Element
kde ComponentClass je jméno třídy komponenty,
Element
je jméno vlastnosti, metody nebo události a Type je Property,
Method
nebo Event.
Např. pro vlastnost nazvanou BackgroundColor komponenty
nazvané
TMyGrid tato poznámka pod čarou je:
TMyGrid_BackgroundColor;BackgroundColor_Property;BackgroundColor
Pro přidání našeho nápovědného souboru k nápovědnému
souboru C++ Builderu použijeme utilitu OpenHelp, kterou zpřístupníme
volbou Help | Customize.
Přidání editorů vlastností
Inspektor objektů poskytuje možnost editace pro všechny typy
vlastností. Nicméně můžeme poskytnout alternativní editor pro konkrétní
vlastnost zápisem a registrací editoru vlastnosti. Můžeme registrovat editor
vlastnosti, který lze použít pouze na vlastnosti ve vytvářené komponentě,
ale můžeme také vytvořit editor, který lze použít na všechny vlastnosti
jistého typu.
V nejjednodušší úrovni editor vlastnosti může operovat
jedním nebo oběma z těchto způsobů: zobrazovat a zpřístupnit k editování
uživateli současnou hodnotu jako textový řetězec a zobrazit dialogové okno
provádějící některé typy editace. V závislosti na editované vlastnosti
je užitečné poskytnout jeden nebo oba způsoby.
Zápis editoru vlastnosti vyžaduje pět kroků:
-
Odvození třídy
editoru vlastnosti
-
Editace vlastnosti
jako text
-
Editace vlastnosti
jako celek
-
Specifikaci atributů
editoru
-
Registrace editoru
vlastnosti.
Odvození třídy editoru vlastnosti
Soubor DSGNINTF.HPP definuje několik typů editorů vlastnosti,
všechny jsou odvozeny od TPropetryEditor. Když vytváříme editor
vlastnosti, můžeme třídu editoru vlastnosti odvodit přímo od TPropertyEditor
nebo nepřímo od jednoho z typů editorů vlastnosti popsaných v další tabulce
(odvozujeme nový objekt od jednoho z existujícího typu editoru vlastnosti).
Soubor DSGNINTF.HPP také definuje některé velmi specializované editory
vlastností používané pro unikátní vlastnosti jako je např. jméno komponenty.
Typ |
Edituje vlastnosti |
TOrdinalProperty |
Základ pro všechny editory vlastností ordinálních typů
(celočíselné, znakové a výčtové vlastnosti). |
TIntegerProperty |
Všechny celočíselné typy včetně předdefinovaných a uživatelem
definovaných intervalů. |
TCharPropetry |
Typ Char a intervaly Char, jako ?A?..?Z?. |
TEnumProperty |
Libovolný výčtový typ. |
TFloatProperty |
Libovolné reálné číslo. |
TStringPropetry |
Řetězce. |
TSetElementProperty |
Individuální prvek v množině, zobrazovaný jako logická
hodnota. |
TSetPropetry |
Všechny množiny. Množiny nejsou přímo editovatelné, ale
můžeme ji rozšířit na seznam prvků množiny. |
TClassPropetry |
Objekty. Zobrazí jméno typu objektu a umožňuje expandovat
vlastnosti objektu. |
TMethodProperty |
Ukazatelé metod. |
TComponentProperty |
Komponenty na formuláři. Uživatel nemůže editovat vlastnosti
komponent, ale může ukazovat na specifické komponenty kompatibilního typu. |
TColorPropetry |
Komponenta barev. Roletový seznam obsahuje konstanty
barev. Dvojité kliknutí otevírá dialogové okno výběru barvy. |
TFontNamePropetry |
Jména písma. Roletový seznam obsahuje všechna aktuálně
instalovaná písma. |
TFontProperty |
Písma. Umožňuje rozšířit na individuální vlastnosti písma
stejně jako přístup k dialogovému oknu písma. |
Následuje deklarace třídy jednoduchého editoru vlastnosti
nazvaného
TMujEditorVlastnosti:
class PACKAGE TMujEditorVlastnosti : public
TPropertyEditor
{
typedef TPropertyEditor inherited;
public:
virtual bool __fastcall AllEqual(void);
virtual System::AnsiString __fastcall
GetValue(void);
virtual void __fastcall SetValue(const
System::AnsiString Value);
__fastcall virtual ~TMujEditorVlastnosti(void)
{ }
__fastcall TMujEditorVlastnosti(void)
: Dsgnintf::TPropertyEditor() { }
};
Editování vlastnosti jako
text
Všechny vlastnosti musí poskytovat řetězcovou reprezentaci
svých hodnot pro zobrazení v Inspektoru objektů. Mnoho vlastností také
zpřístupňuje uživateli typ v nové hodnotě pro vlastnost. Třída editoru
vlastností poskytuje virtuální metody, které můžeme přepsat pro převod
mezi textovou reprezentací a aktuální hodnotou. Tyto metody jsou nazvané
GetValue
a SetValue. Náš editor vlastnosti také dědí množinu metod používaných
pro přiřazení a čtení jiných typů hodnot (viz následující tabulka):
Typ vlastnosti |
Metoda "Get" |
Metoda "Set" |
reálný |
GetFloatValue |
SetFloatValue |
závěr (událost) |
GetMethodValue |
SetMethodValue |
ordinální typ |
GetOrdValue |
SetOrdValue |
řetězec |
GetStrValue |
SetStrValue |
Když předefinujeme metodu GetValue, můžeme stále
volat jednu z metod "Get" a když předefinujeme metodu SetValue,
můžeme stále volat jednu z metod "Set".
Metoda GetValue editoru vlastnosti vrací řetězec,
který reprezentuje současnou hodnotu vlastnosti. Inspektor objektů používá
tento řetězec ve sloupci hodnot pro vlastnost. Implicitně GetValue
vrací "unknown".
K poskytnutí řetězcové reprezentace pro naši vlastnost,
předefinujeme metodu GetValue editoru vlastnosti. Jestliže vlastnost
nemá řetězcovou hodnotu, naše GetValue musí převést hodnotu na její
řetězcovou reprezentaci.
Metoda SetValue editoru vlastnosti přebírá řetězec
zapsaný uživatelem v Inspektoru objektů, převádí jej na příslušný typ a
nastavuje hodnotu vlastnosti. Jestliže řetězec nemá reprezentaci příslušné
hodnoty pro vlastnost,
SetValue generuje výjimku a nevhodnou hodnotu
nepoužije. Pro přečtení řetězcové hodnoty z vlastnosti, předefinujeme metodu
SetValue
editoru vlastnosti.
SetValue musí převádět řetězec a ověřovat hodnotu
před voláním jedné z metod "Set".
Editování vlastnosti jako
celek
Volitelně můžeme poskytnout dialogové okno, ve kterém může
uživatel viditelně editovat vlastnost. Toto je užitečné pro editory vlastnosti
jejichž vlastnosti jsou třídy. Příkladem je vlastnost Font, pro
kterou uživatel může otevřít dialogové okno k volbě všech atributů písma.
K poskytnutí dialogového okna celkového editoru vlastnosti, předefinujeme
metodu Edit třídy editoru vlastnosti.
Metoda Edit používá stejné metody "Get" a
"Set"
jako jsou použity v metodách GetValue a SetValue (metoda
Edit
volá obě tyto metody). Jelikož editor je specifického typu, obvykle není
potřeba převádět hodnotu vlastnosti na řetězec. Když uživatel klikne na
tlačítko '...' vedle vlastnosti nebo dvojitě klikne ve sloupci hodnot,
Inspektor objektů volá metodu Edit editoru vlastnosti. V implementaci
metody Edit provedeme tyto kroky: Vytvoříme náš editor, přečteme
současné hodnoty a přiřadíme je metodou "Get", když uživatel změní
některou hodnotu, přiřadíme tuto hodnotu pomocí metody
"Set" a zrušíme
editor.
Specifikování atributů editoru
Editor vlastnosti musí poskytovat informace, které Inspektor
objektů může použít k určení zobrazeného nástroje. Např. Inspektor objektů
musí znát, zda vlastnost má podvlastnosti nebo zda může zobrazit seznam
možných hodnot. Pro specifikaci atributů editoru, předefinujeme metodu
GetAttributes
třídy editoru. GetAttributes je metoda vracející množinu hodnot
typu TPropertyAttributes, která může obsahovat některé nebo všechny
následující hodnoty:
Příznak |
Význam, je-li vložen |
Ovlivňuje metodu |
paValueList |
Editor může zobrazit seznam výčtových hodnot. |
GetValues |
paSubProperties |
Vlastnost má podvlastnosti, které může zobrazit. |
GetProperties |
paDialog |
Editor může pro editaci zobrazit dialogové okno. |
Edit |
paMultiSelect |
Uživatel může vybrat více než jeden prvek. |
|
paAutoUpdate |
Aktualizuje komponentu po každé změně, namísto čekání
na schválení hodnoty. |
SetValue |
paSortList |
Inspektor objektů seřadí seznam hodnot. |
|
paReadOnly |
Uživatel nemůže modifikovat hodnotu vlastnosti. |
|
paRevertable |
Povoluje volbu Revert to Inherited v místní nabídce Inspektora
objektů (návrat k předchozí nebo implicitní hodnotě). |
|
Vlastnost Color má několik možností, jak ji uživatel
zadá v Inspektoru objektů: zápisem, výběrem ze seznamu a editorem. Metoda
GetAttributes
TColorProperty, tedy obsahuje několik atributů ve své návratové hodnotě:
virtual __fastcall TPropertyAttributes TColorProperty::GetAttributes()
{
return TPropertyAttributes()<<paMultiSelect<<paDialog<<paValueList;
}
Registrace editoru vlastnosti
Vytvořený editor vlastnosti se musí v C++ Builderu registrovat.
Registrací editoru vlastnosti přiřadíme typ vlastnosti k editoru vlastnosti.
Můžeme registrovat editor se všemi vlastnostmi daného typu nebo s jistou
vlastností jistého typu komponenty. K registraci editoru vlastnosti voláme
funkci
RegisterPropetryEditor.
Tato funkce má čtyři parametry: Prvním je ukazatel na
informace o typu pro editovanou vlastnost. To je vždy volání funkce __typeinfo,
např.
__typeinfo(TMojeKomponenta). Druhým je typ komponenty, na
který je tento editor aplikován. Jestliže tento parametr je NULL, editor
je použitelný na všechny vlastnosti daného typu. Třetím parametrem je jméno
vlastnosti. Tento parametr má význam pouze, jestliže předchozí parametr
specifikuje konkrétní typ komponenty. V tomto případě, můžeme specifikovat
jméno konkrétní vlastnosti v typu komponenty, se kterou tento editor pracuje.
Posledním parametrem je typ editoru vlastnosti použitý pro editování specifikované
vlastnosti.
Následuje ukázka funkce, která registruje editory pro
některé standardní komponenty na Paletě komponent:
namespace Newcomp
{
void __fastcall PACKAGE Register()
{
RegisterPropertyEditor(__typeinfo(TComponent),
0L, "",
__classid(TComponentProperty));
RegisterPropertyEditor(__typeinfo(TComponentName),
TComponent,
"Name", __classid(TComponentNameProperty));
RegisterPropertyEditor(__typeinfo(TMenuItem),
TMenu, "",
__classid(TMenuItemProperty));
}
}
Tři příkazy v této funkci ukazují různé použití RegisterPropertyEditor:
První příkaz je nejtypičtější. Registruje editor vlastnosti TComponentProperty
pro všechny vlastnosti typu TComponent (nebo potomků TComponent,
které nemají registrován vlastní editor). Obecně, když registrujeme editor
vlastnosti, vytvoříme editor pro jistý typ a chceme jej použít pro všechny
vlastnosti tohoto typu, pak jako druhý parametr použijeme NULL a jako třetí
parametr prázdný řetězec. Druhý příkaz je nejspecifičtějším typem registrace.
Registruje editor pro jistou vlastnost v jistém typu komponenty. V tomto
případě je to editor pro vlastnost Name (typ TComponentName)
všech komponent. Třetí příklad je specifičtější než první, ale není limitován
jako druhý. Registruje editor pro všechny vlastnosti typu TMenuItem
v komponentách typu TMenu.
Přidávání editoru komponenty
Editory komponent určují co nastane, když dvojitě klikneme
na komponentu na Návrhovém formuláři a přidává příkazy do místní nabídky,
která je zobrazena při kliknutí pravým tlačítkem myši na komponentě. Umožňuje
také kopírovat naši komponentu do schránky Windows v přizpůsobeném formátu.
Pokud naší komponentě nedáme editor komponenty, pak C++
Builder použije implicitní editor komponenty. Implicitní editor komponenty
je implementován třídou TDefaultEditor. TDefaultEditor nepřidává
žádné nové prvky v místní nabídce. Když na komponentě dvojitě klikneme,
pak TDefaultEditor hledá vlastnosti komponenty a generuje (nebo
přechází na) první obsluhu události.
K vytvoření editoru komponenty pro naší komponentu, musíme
odvodit novou třídu od TComponentEditor a potom provést jednu nebo
více věcí z:
Z předchozího seznamu je požadována pouze registrace Editoru
komponenty.
Přidávání prvku k místní
nabídce
Když uživatel klikne na komponentě pravým tlačítkem myši,
pak jsou volány metody GetVerbCount a GetVerb Editoru komponenty
k vytvoření místní nabídky. Tyto metody můžeme přepsat pro přidání příkazu
k místní nabídce.
Metodu GetVerbCount přepíšeme, aby vracela počet
příkazů přidaných do místní nabídky. Metodu GetVerb přepíšeme, aby
vracela řetězce, které chceme přidat pro každý z těchto příkazů. Znak &
v řetězci, způsobí, že následující znak bude podtržen a bude pracovat jako
zkracovací klávesa pro tento prvek nabídky. Na konec příkazů, které zobrazují
dialogové okno zapíšeme .... GetVerb má jeden parametr, který
specifikuje index příkazu.
Následující kód přepisuje metody GetVerbCount
a GetVerb pro přidání dvou příkazů k místní nabídce:
int __fastcall TMujEditor::GetVerbCount(void)
{
return 2;
}
System::AnsiString __fastcall TMujEditor::GetVerb(int
Index)
{
switch (Index)
{
case 0: return "&DoThis
... "; break;
case 1: return "Do&That";
break;
}
}
Poznámka: Ujistíme se, že metoda GetVerb
vrací hodnotu pro každý možný index specifikovaný GetVerbCount.
Když příkaz poskytnutý GetVerb je vybrán v Návrhovém
formuláři, pak je volána metoda ExecuteVerb. Pro každý příkaz poskytnutý
v metodě GetVerb, implementujeme akci v metodě ExecuteVerb.
Můžeme přistupovat ke komponentě, která bude editována pomocí vlastnosti
Component
editoru.
Následující metoda ExecuteVerb implementuje příkazy
pro předchozí metodu GetVerb:
void __fastcall TMujEditor::ExecuteVerb(int
Index)
{
switch (Index)
{
case 0:
TMujDialog
*MySpecialDialog = new TMujDialog();
MySpecialDialog->Execute();
// používáme
vlastnost Component pro přístup k instanci TMojeComponenta
((TMojeComponenta
*)Component)->ThisProperty = MySpecialDialog->ReturnValue;
delete MySpecialDialog;
break;
case 1:
That();
// volání metody "That"
break;
}
}
Změna chování dvojitého
kliknutí
Když na komponentu dvojitě klikneme, pak je volána metoda
Edit
Editoru komponenty. Implicitně metoda Edit provádí první příkaz
přidaný do místní nabídky. I když provádění prvního příkazu je obvykle
vhodná myšlenka, někdy toto implicitní chování můžeme potřebovat změnit.
Např. můžeme požadovat alternativní chování pokud do místní nabídky nepřidáme
žádný příkaz nebo můžeme požadovat zobrazení dialogového okna, které spojuje
několik příkazů, když na komponentě dvojitě klikneme.
Přepsáním metody Edit specifikujeme nové chování
pro dvojité kliknutí na komponentě. Např. následující metoda Edit
způsobí zobrazení dialogového okna písma při dvojitém kliknutí na komponentě:
void __fastcall TMujEditor::Edit(void)
{
TFontDialog *pFontDlg = new TFontDialog(NULL);
pFontDlg->Execute();
// vlastnost Component používáme pro
přístup k instanci TMojeComponenta
((TMokeKomponenta *)Component)->Font
= pFontDlg->Font;
delete pFontDlg;
}
Pokud chceme přejít do Editoru kódu na obsluhu události
při dvojitém kliknutí na komponentě, pak použijeme jako třídu předka TDefaultEditor
namísto TComponentEditor. Potom místo přepsání metody Edit
přepíšeme chráněnou metodu TDefaultEditor::EditProperty. EditProperty
prochází obsluhami událostí komponenty a použije první nalezenou. Toto
hledání můžeme změnit. Např.
void __fastcall TMujEditor::EditProperty(TPropertyEditor*
PropertyEditor, bool &Continue,
bool &FreeEditor)
{
if (PropertyEditor->ClassNameis("TMethodProperty")
&&
CompareText(PropertyEditor->GetName,
"OnSpecialEvent") == 0)
{
TDefaultEditor::EditProperty(PropertyEditor,
Continue, FreeEditor);
}
}
Přídávání formátu schránky
Implicitně, když uživatel zvolí Copy při vybrané komponentě,
pak komponenta je kopírována do schránky v interním formátu C++ Builderu.
Potom ji můžeme vložit na jiný formulář nebo datový modul. Naši komponentu
můžeme kopírovat v dalších formátech do schránky přepsáním metody Copy.
Např. následující metoda Copy umožňuje komponentě
TImage
kopírovat svůj obrázek do schránky. Tento obrázek je ignorován IDE C++
Builderu, ale může být vkládán do jiných aplikací.
void __fastcall TMujEditor::Copy(void)
{
WORD AFormat;
int AData;
HPALETTE APalette;
((TImage*)Component)->Picture->SaveToClipboardFormat(AFormat,AData,
APalette);
TClipboard *pClip = Clipboard(); //
nevyprazdňuje schránku
pClip->SetAsHandle(AFormat, AData);
}
Registrace editoru komponenty
Když již máme definován editor komponenty, můžeme jej registrovat
pro práci s jistou třídou komponenty. Registrovaný editor komponenty je
vytvořen pro každou komponentu, když je vybrána na návrhovém formuláři.
K vytvoření asociace mezi Editorem komponenty a třídou
komponenty voláme RegisterComponentEditor. Tato funkce přebírá jméno
třídy komponenty, která používá editor a jméno třídy Editoru komponenty,
kterou máme definovanou. Např. následující příkaz registruje třídu Editoru
komponenty nazvanou TMujEditor pro práci se všemi komponentami typu
TMojeKomponenta:
RegisterComponentEditor(__classid( TMojeKomponenta),
__classid(TMujEditor));
Volání RegisterComponentEditor umístíme do jmenného
prostoru kde registrujeme naši komponentu.
Pokud nová komponenta nazvaná TMojeKomponenta
a její editor
TMujEditor jsou implementovány v NovaKomp.cpp,
pak následující kód (v NovaKomp.cpp) registruje komponentu a spojuje
ji s Editorem komponenty:
namespace Novakomp
{
void __fastcall PACKAGE Register()
{
TComponentClass
classes[1] = {__classid(TMojeKComponenta)};
RegisterComponents("Různé",
classes, 0);
RegisterComponentEditor(classes[0],
__classid(TMujEditor));
}
}
Překlad komponent do balíčků
Když již komponenty jsou registrované, pak je musíme přeložit
jako balíčky a to dříve než je můžeme instalovat v IDE. Balíček může obsahovat
jednu nebo několik komponent a také uživatelských editorů vlastností.
K vytvoření a překladu balíčku, umístíme zdrojové kódy
jednotek našich komponent do seznamu obsahu balíčku. Pokud naše komponenty
závisí na dalších balíčcích, vložíme tyto balíčky do seznamu požadavků.
Přeložený balíček s komponentami již můžeme instalovat na Paletu komponent.
Obecným problémem při registraci a instalování uživatelské
komponenty je to, že komponenta se nezobrazuje v seznamu komponent po úspěšně
instalovaném balíčku. Následuje přehled obecných příčin nezobrazování komponenty
v seznamu nebo na paletě:
-
Chybějící modifikátor PACKAGE u funkce Register.
-
Chybějící modifikátor PACKAGE u třídy.
-
Chybějící #pragma package(smart_init) ve zdrojovém
kódu C++.
-
Funkce Register není nalezena ve jmenném prostoru
se stejným jménem jako je jméno modulu zdrojového kódu.
-
Register není úspěšně exportován. Použijte tdump
na soubor BPL k prohlédnutí exportovaných funkcí:
tdump -ebpl mypack.bpl mypack.dmp
V sekci exports vidíme zda funkce Register
(ve jmenném prostoru) byla exportována
-
Když pracujeme s připojenou databází, je často užitečné mít ovladač spojen
s nějakou položkou databáze. Aplikace tedy může založit propojení mezi
ovladačem a nějakou částí databáze. C++ Builder obsahuje datové ovladače
typů editačních oken, seznamů, kombinovaných oken a mřížek. Můžeme také
vytvořit svůj vlastní datový ovladač. Je několik stupňů závislosti dat
datových ovladačů. Nejjednodušší je datová závislost pouze pro čtení, nebo
prohlížení dat, a umožnění reakce na aktuální stav databáze. Složitější
je editovatelná datová závislost, kde uživatel může editovat hodnoty v
databázi manipulací s ovladačem.
Nyní se pokusíme vytvořit ovladač určený pouze pro čtení, který je
spojen s jednou položkou v databázi. Ovladač bude používat kalendář vytvořený
v předchozí kapitole.
Vytvoření datového ovladače kalendáře provedeme v následujících krocích:
vytvoříme a registrujeme komponentu, uděláme ovladač určeny pouze pro čtení,
přidáme datové propojení a reakce na změny dat.
Použijeme obecný postup s těmito specifikami: jednotku komponenty nazveme
DBCal,
odvodíme nový typ komponenty nazvaný TDBCalendar od
TSampleCalendar
a registrujeme TDBCalendar na stránce Samples Palety komponent.
Výsledek naší práce je (nejprve je uveden výpis hlavičkového souboru
jednotky):
#ifndef DBCalH
#define DBCalH
#include <vcl\sysutils.hpp>
#include <vcl\controls.hpp>
#include <vcl\classes.hpp>
#include <vcl\forms.hpp>
#include <vcl\grids.hpp>
#include "CALSAMP.h"
class PACKAGE TDBCalendar : public TSampleCalendar
{
private:
protected:
public:
__published:
};
#endif
CPP soubor vypadá takto:
#pragma link "Calsamp" // vazba na
TSampleCalendar
#include <vcl\vcl.h>
#pragma hdrstop
#include "DBCal.h"
#pragma package(smart_init);
static inline TDBCalendar *ValidCtrCheck()
{
return new TDBCalendar(NULL);
}
namespace Dbcal
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1]
= {__classid(TDBCalendar)};
RegisterComponents("Samples",
classes, 0);
}
}
-
Jelikož tento kalendář bude určen pouze pro čtení, je vhodné znemožnit
uživateli provádění změn v ovladači. Provedeme to ve dvou krocích: přidáme
vlastnost ReadOnly a dovolíme potřebné aktualizace. Když tato vlastnost
je nastavena na true, jsou všechny buňky v ovladači nevybíratelné.
Přidáme deklaraci vlastnosti a soukromou položku k uložení hodnoty:
class PACKAGE TDBCalendar : public TSampleCalendar
{
private:
bool FReadOnly;
protected:
public:
__fastcall TDBCalendar(TComponent*
Owner);
__published:
__property bool ReadOnly={read=FReadOnly,write=FReadOnly,default=true};
};
Zapíšeme definici konstruktoru:
__fastcall TDBCalendar::TDBCalendar(TComponent*
Owner)
: TSampleCalendar(Owner)
{
FReadOnly = true;
}
a předefinujeme metodu SelectCell k zákazu výběru, jestliže
ovladač je pouze pro čtení.
bool __fastcall TDBCalendar::SelectCell(long
ACol, long ARow)
{
if (FReadOnly) return false;
return TSampleCalendar::SelectCell(ACol,
ARow);
}
Nesmíme zapomenout přidat deklaraci SelectCell k deklaraci třídy
TDBCalendar.
Jestliže nyní přidáme kalendář na formulář, zjistíme, že komponenta ignoruje
kliknutí a stisky kurzorových kláves.
Kalendář pouze pro čtení používá metodu SelectCell pro všechny
typy změn, včetně nastavování vlastností
Row a Col. Metoda
UdpateCalendar
nastavuje Row a Col pokaždé, když se změní datum, ale jelikož
SelectCell
nepovolí změny, výběr se nemění, i když se změní datum. K omezení tohoto
absolutního zákazu změn, můžeme přidat interní logickou položku ke kalendáři
a povolit změny, když je tato položka nastavena na
true:
class PACKAGE TDBCalendar : public TSampleCalendar
{
private:
bool FUpdating;
public:
virtual void __fastcall UpdateCalendar();
};
bool __fastcall TDBCalendar::SelectCell(long
ACol, long ARow)
{
if (!FUpdating && FReadOnly)
return false;
return TSampleCalendar::SelectCell(ACol,
ARow);
}
void __fastcall TDBCalendar::UpdateCalendar()
{
FUpdating = true;
try
{
TSampleCalendar::UpdateCalendar();
}
catch(...)
{
FUpdating = false;
throw;
}
FUpdating = false;
}
Kalendář stále neumožňuje uživateli provádět změny datumu, ale již
správně reaguje na změny datumu provedené změnou vlastností datumu. Dále
potřebujeme ke kalendáři přidat schopnost prohlížet datumy.
-
Propojení mezi ovladačem a databází je obsluhováno objektem nazvaným datový
spoj. C++ Builder poskytuje několik typů datových spojů. Objekt datového
spoje, který propojuje ovladač s jednou položkou v databázi je TFieldDataLink.
Jsou také datové spoje pro celé tabulky. Objekt datového ovladače vlastní
svůj objekt datového spoje. Tj. ovladač má odpovědnost za vytvoření i uvolnění
datového spoje. K vytvoření datového spoje jako vlastněného objektu provedeme
tři kroky: deklarujeme objektovou položku, deklarujeme přístupové vlastnosti
a inicializujeme datový spoj.
Komponenta vyžaduje položku pro každý svůj vlastněný objekt. V našem
případě kalendář potřebuje položku typu TFieldDataLink pro svůj
datový spoj:
class PACKAGE TDBCalendar : public TSampleCalendar
{
private:
TFieldDataLink *FDataLink;
};
Dříve než můžeme přeložit aplikaci, musíme vložit hlavičkové soubory
DB.HPP
a DBTables.HPP do hlavičkového souboru naší jednotky.
Každý datový ovladač má vlastnost DataSource, která specifikuje
který objekt datového zdroje v aplikaci poskytuje data ovladači. Dále ovladač,
který přistupuje k samostatné položce vyžaduje vlastnost DataField
ke specifikaci položky datového zdroje. Tyto přístupové vlastnosti neposkytují
přístup k vlastněnému objektu sami, ale odpovídajícími vlastnostmi ve vlastněném
objektu. Deklarujeme vlastnosti DataSource a DataField a
jejich implementační metody a zapíšeme metody jako ?předávající? metody
k odpovídajícím vlastnostem objektu datového spoje.
class PACKAGE TDBCalendar : public TSampleCalendar
{
private:
AnsiString __fastcall GetDataField();
TDataSource *__fastcall GetDataSource();
void __fastcall SetDataField(const
AnsiString Value);
void __fastcall SetDataSource(TDataSource
*Value);
__published:
__property AnsiString DataField =
{read=GetDataField,
write=SetDataField, nodefault};
__property TDataSource *DataSource
= {read=GetDataSource,
write=SetDataSource, nodefault};
};
AnsiString __fastcall TDBCalendar::GetDataField()
{
return FDataLink->FieldName;
}
TDataSource *__fastcall TDBCalendar::GetDataSource()
{
return FDataLink->DataSource;
}
void __fastcall TDBCalendar::SetDataField(const
AnsiString Value)
{
FDataLink->FieldName = Value;
}
void __fastcall TDBCalendar::SetDataSource(TDataSource
*Value)
{
if(Value != NULL)
Value->FreeNotification(this);
FDataLink->DataSource = Value;
}
Nyní, když máme vytvořeno spojení mezi kalendářem a jeho datovým spojem,
je jeden velmi důležitý krok. Musíme při vytváření ovladače kalendáře vytvořit
objekt datového spoje a datový spoj zrušit před zrušením kalendáře.
Datový ovladač vyžaduje přístup k svému datovému spoji prostřednictvím
své existence, a musíme tedy vytvořit objekt datového spoje v jeho vlastním
konstruktoru a zrušit objekt datového spoje před svým samotným zrušením.
Přepíšeme tedy konstruktor a destruktor kalendáře k vytváření a rušení
objektu datového spoje.
class PACKAGE TDBCalendar : public TSampleCalendar
{
public:
__fastcall TDBCalendar(TComponent*
Owner);
__fastcall ~TDBCalendar();
};
__fastcall TDBCalendar::TDBCalendar(TComponent*
Owner)
: TSampleCalendar(Owner)
{
FReadOnly = true;
FDataLink = new TFieldDataLink();
FDataLink->Control = this;
}
__fastcall TDBCalendar::~TDBCalendar()
{
FDataLink->Control = NULL;
FDataLink-OnUpdateData = NULL;
delete FDataLink;
}
Nyní máme kompletní datový spoj, ale nemáme možnost řídit, která data
budou čtena z připojené položky.
-
Náš ovladač má datový spoj a vlastnosti specifikující datový zdroj a datovou
položku a musíme ještě vytvořit reakce na změny v datech této datové položky,
neboť se můžeme přesunout na jiný záznam. Všechny objekty datových spojů
mají události nazvané OnDataChange. Když datový zdroj indikuje změnu
ve svých datech, objekt datového spoje volá obsluhu událostí připojenou
k této události. K aktualizaci ovladače v reakci na datové změny, připojíme
obsluhu k události OnDataChange datového spoje.
V našem případě, přidáme metodu DataChange ke kalendáři a určíme
ji jako obsluhu pro OnDataChange datového spoje v konstruktoru.
V destruktoru musíme obsluhu odpojit a to před zrušením objektu.
class PACKAGE TDBCalendar : public TSampleCalendar
{
private:
void __fastcall DataChange(TObject
*Sender);
};
__fastcall TDBCalendar::TDBCalendar(TComponent*
Owner)
: TSampleCalendar(Owner)
{
FReadOnly = true;
FDataLink = new TFieldDataLink();
FDataLink->Control = this;
FDataLink->OnDataChange = DataChange;
}
__fastcall TDBCalendar::~TDBCalendar()
{
FDataLink->Control = NULL;
FDataLink-OnUpdateData = NULL;
FDataLink->OnDataChange = NULL;
delete FDataLink;
}
void __fastcall TDBCalendar::DataChange(TObject
*Sender)
{
if (FDataLink->Field == NULL) CalendarDate
= 0;
else CalendarDate = FDataLink->Field->AsDateTime;
}
Tím je zobrazovací databázový ovladač kalendáře hotov.
-
Pokuste se u právě vytvořené komponenty zadat bitovou mapu zobrazenou na
paletě a vytvořit nápovědu, kterou zapojíte do nápovědného systému C++
Builderu.
   |
8. Udělání komponenty dostupné
při návrhu
|