8. Udělání komponenty dostupné při návrhu

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:
  1. 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.

  2. 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.
  3. Každá komponenta musí mít sekundární navigační prvky nápovědy. Jedná se o:
  4. 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?.
  5. 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.

  6. 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ů:
  1. Odvození třídy editoru vlastnosti
  2. Editace vlastnosti jako text
  3. Editace vlastnosti jako celek
  4. Specifikaci atributů editoru
  5. 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ě:
  1. 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.

  2. 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);
      }
    }
  3. 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:

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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