Tvorba komponent pro C++ Builder - komponenta HyperLink

V tomto pokračování si ukážeme vytvoření jednoduché komponenty, která se bude chovat jako hypertextový odkaz.
Odvodíme si ji od komponenty TStaticText, což je komponenta podobná často používanému TLabel, avšak je odvozená od TWinControl, tedy je to standardní okno se vším co k tomu patří.

Vytvoření balíčku

Nejdříve si ale musíme ukázat (někteří třeba s C++ Builderem začínají) jak vytvořit balíček a do něj přidat novou komponentu. Pro vytvoření nového balíčku vybereme v nabídce „File“ -> „New“ a na záložce „New“ vybereme položku „Package“, tedy balíček, jak vidíte na následujícím obrázku:

Nyní zbývá balíček uložit a máme jej připravený pro přidávání nových komponent. Na následujícím obrázku vidíte okno, nazývejme ho třeba „manažer balíčku“, po jeho uložení. Název a cestu si samozřejmě můžete zvolit podle libosti.

Přidání nové komponenty do balíčku

Nyní stačí stisknout tlačítko Add, na dialogu vybrat záložku „New Component“ a vyplnit požadované informace. Položka „Ancestor type“ určuje již zmiňovaného předka nové komponenty. „Class Name“ je jméno třídy nové komponenty. Podle dokumentace sice může začínat libovolným písmenem, ale z vlastní zkušenosti doporučuji C++ Builder příliš nedráždit a držet se borlandské konvence Txxxxx. Ke jménu třídy ještě tolik, že je dobré si uvědomit, že není možné mít (byť v různých balíčcích) 2 komponenty se stejným názvem. Proto pokud použijete příliš jednoduchý název, je možné, že v budoucnu si budete chtít třeba nainstalovat nějakou komponentu třetí strany, které se bude jmenovat stejně, a dostanete se do problému. Tedy nedoporučuji třeba názvy jako TColorButton, TTransparentEdit apod. Nejjednodušší je před tento mnemotechnický název ještě „předhodit“ nějakou svoji zkratku, jako název firmy apod. Položka „Palette Page“ určuje název záložky na paletě komponent, na které se nová komponenta po instalaci objeví. Pokud tato záložka neexistuje, bude vytvořena nová. „Unit file name“ je cesta ke zdrojovému souboru komponenty. „Search path“ za nás předvyplní C++ Builder, v naprosté většině případů ji není třeba nijak upravovat. Jedná se o seznam cest k používaným knihovnám, hlavičkovým souborům apod. Příklad vyplnění údajů naší nové komponenty vidíte na následujícím obrázku:

Po potvrzení těchto údajů C++ Builder vygeneruje soubory .cpp a .h, které představují zdrojový kód třídy komponenty.

Dříve než konečně přejdeme k vlastnímu zdrojovému kódu, ještě si ukážeme, jak si zjednodušit proces ladění a zkoušení komponenty. K ladění samozřejmě budeme potřebovat testovací aplikaci (projekt). Je výhodné vytvořit si skupinu projektů („Project Group“), v které budeme mít současně náš balíček a testovací projekt. Pokud s C++ Builderem začínáte, ukážu jak na to: Zavřete vše otevřené („File“ -> „Close Al“), a vyberte „New“, a na záložce „New“ vyberte položku „Project Group“. Vytvoří se okno „Projekt manager“. Tuto prázdnou skupinu projektů kamkoli uložte (File -> Save) jako soubor .bpg (Borlang Project Group). Nyní v položce nabídky „Project“ máte (mimo jiné) volby „Add New Project“ a „Add Existing Project“, kterými můžete do skupiny projektů přidat existující projekt (což bude případ předtím vytvořeného balíčku) nebo vytvořit nový projekt (což bude asi případ testovací aplikace). Jak uvidíte, můžete pak snadno přepínat mezi projektem balíčku, kde provedete úpravu kódu, balíček překompilujete, přepnete se do testovací aplikace, ve které budete mít formulář s vyvíjenou komponentou, který můžete hned přeložit i spustit. Vše si samozřejmě ukážeme v praxi.

Nastavení výchozích vlastností komponenty

Prvním krokem nyní bude nastavit výchozí hodnoty již existujících property tak, aby byly v souladu s naším záměrem a programátor-designér měl co nejméně mechanické práce s jejím nastavováním. Vyplatí se věnovat při tvorbě komponenty čas přemýšlením nad optimálním nastavením těchto výchozích hodnot, určitě pak ušetříme více času při vlastním použití komponenty. V našem případě budeme chtít, aby se text vypadal jako běžný hypertextový odkaz, tedy byl v modré barvě a podtržený. Zda má být i tučný, nechám na Vaší úvaze, já jsem ho jako tučný nastavil. Navíc jsem přidal jako výchozí zobrazování „hintu“. Toto výchozí nastavení provedeme v konstruktoru třídy komponenty.

__fastcall TRPJHyperlink::TRPJHyperlink(TComponent* Owner)
  : TStaticText(Owner)
{
  Font->Color = clBlue;
  Font->Style = TFontStyles() << fsBold << fsUnderline;
  Cursor = crHandPoint;
  ShowHint = true;
}

Přidání nové vlastnosti (property)

Nyní přejděme k problematice přidání vlastních property, tedy vlastností. V tomto článku se zatím omezíme na jedinou novou vlastnost, a tou bude samozřejmě umístění cíle hypertextového odkazu. Je asi zřejmé, že tato vlastnost bude typu AnsiString. Pro přidání property můžeme použít ClassExplorer. Nastavíme jako aktuální projekt náš balíček, v ClassExploreru klikneme pravým tlačítkem na třídě TRPJHyperlink a takto vyplníme vyvolaný dialog:

Význam položek „Property Name“ a „Type“ je asi jasný. Dále musíme zaškrtnout „viditelnost“ jako „Published“, což znamená, že tato vlastnost se objeví v ObjectInspectoru a lze ji tedy měnit v době návrhu. Hodnota property je uvnitř třídy „uložena“ v proměnné FHyperlink. Budeme se držet standardu a nechávat jména těchto proměnných vždy tak, jak je nabídne ClassWizard, tedy s velkým „F“ před názvem vlastnosti. Dále máme možnost vytvořit metody typu „Set“ a „Get“, v kterých budeme moci nějak reagovat v okamžicích, kdy dochází k nastavení, tedy změně a čtení hodnoty property do (nebo z) této proměnné (Fxxxxx). V tomto případě si necháme vygenerovat funkci SetHyperlink, za chvíli uvidíme proč.

Když se nyní podíváme do zdrojového kódu, uvidíme, co nám ClassExplorer vygeneroval při tomto přidání nové property. Deklarace třídy nyní vypadá takto (přidaný kód je tučný):

class PACKAGE TRPJHyperlink : public TStaticText
{
private:
  AnsiString FHyperlink;
  void __fastcall SetHyperlink(AnsiString value);
protected:
public:
  __fastcall TRPJHyperlink(TComponent* Owner);
__published:
  __property AnsiString Hyperlink = { read=FHyperlink, write=SetHyperlink };
};

V souboru RPJHyperlink.cpp nám přibude tato funkce:

void __fastcall TRPJHyperlink::SetHyperlink(AnsiString value)
{
  if (FHyperlink != value) {
    FHyperlink = value;
  }
}

Vidíme, že v této funkci právě dochází k uložení hodnoty do té proměnné typu Fxxxx. Tato funkce je volána v okamžiku, kdy třeba někde v programu přiřadíme této property nějakou hodnotu, třeba takto:

Hyperlink1->Hyperlink = „nejaky cil odkazu“;

Ale nejen tehdy. Tato funkce je volána, i v době návrhu, když hodnotu příslušné property nastavíme v ObjectInspectoru. Díky této funkci máme například možnost nějak otestovat „vstupní“ hodnotu a třeba ji neakceptovat, tedy nechat hodnotu property beze změny, pokud nevyhovuje našim požadavkům. Možná se vám bude zdát divné, jak může být funkce volána v době návrhu, když program není spuštěn. Ve skutečnosti i v době návrhu je kód balíčku načten v paměti (uvnitř IDE) a může být tedy volán. Proto také při vývoji komponent bývá velmi častým jevem, že při nějaké drobné chybě „sestřelíme“ celé IDE. Proto také doporučuji nastavit automatické ukládání souborů před spuštěním programu, i když ani to nemusí být právě z výše uvedeného důvodu zárukou (a ani nebývá).

Pokud chceme zjistit, zda změna hodnoty property byla vyvolána programátorem v době návrhu, máme k dispozici vlastnost všech komponent VCL, kterou je

__property TComponentState ComponentState = {read=FComponentState, nodefault};

Tato vlastnost určuje jak název napovídá, „stav“ komponenty. Mezi hodnotami, které může obsahovat, je i hodnota csDesigning, která právě určuje, že jsem ve fázi návrhu, a nikoli běhu programu. Nejlépe bude ukázat si to prakticky. Doplníme do „Set“ funkce následující kód:

void __fastcall TRPJHyperlink::SetHyperlink(AnsiString value)
{
  if (FHyperlink != value)
  {
    FHyperlink = value;
    if ( ComponentState.Contains(csDesigning) )
      ShowMessage("Změnil jsi hodnotu hypertextového odkazu");
  }
}

Když se nyní přepnete do testovacího projektu, na jeho formulář umístíte naši komponentu, a v ObjectInspectoru změníte hodnotu property Hyperlink, po jejím potvrzení se nám zobrazí přidaná zpráva (ShowMessage(…)). Když si dále s testovacím projektu zkusíte změnit hodnotu této property v run.-timu, tj. třeba na tlačítko zapsat:

RPJHyperlink1->Hyperlink = "nova hodnota"; správně tušíte, že ke zobrazení zprávy nedojde.

Nyní ale musíme ještě v tomto díle zkompletovat alespoň tu nejzákladnější funkčnost komponenty. V dalším pokračování ji budeme dále rozvíjet.

Je logické, že budeme chtít, aby při kliknutí na hypertextový odkaz se program automaticky pokusil otevřít cíl hypertextového odkazu, aniž by programátor musel nějak ošetřovat událost OnClick, jinak by asi tato komponenta nebyla příliš užitečná. Než k tomu přistoupíme, doplníme ještě do kódu funkce SetHyperlink automatické nastavení Hintu tak, aby zobrazoval hodnotu zadaného hypertextového odkazu. Proto také jsem do konstruktoru mezi výchozí nastavení přidal zobrazování Hintu. „Finální verze“ funkce SetHyperink může vypadat třeba takto:

void __fastcall TRPJHyperlink::SetHyperlink(AnsiString value)
{
  if ( FHyperlink != value )
  {
    FHyperlink = value;
    if ( FHyperlink.IsEmpty() )
      Hint = "Odkaz nezadán";
    else
      Hint = FHyperlink;
  }
}

Nyní tedy zbývá realizovat otevření odkazu. Použijeme zatím ten nejjednodušší způsob, tj. funkci

HINSTANCE ShellExecute(
  HWND    hwnd,
  LPCTSTR  lpVerb,
  LPCTSTR  lpFile,
  LPCTSTR  lpParameters,
  LPCTSTR  lpDirectory,
  INT      nShowCmd
);

Tato funkce nám umožňuje provádět základní operace se soubory v tom širším slova smyslu. Jako „soubor“ může figurovat kromě „klasického“ souboru na disku také třeba http adresa na internetu, příjemce elektronické pošty atd. Pro případ „otevření“, tedy řekněme spuštění uvedeme jako parametr lpVerb hodnotu „open“. Parametry lpParameters a lpDirectory necháme NULL a nShowCmd uvedeme SW_SHOWNORMAL, tedy spuštění okna v „bežné“ velikosti. Podrobnější rozbor této funkce je mimo rámec tohoto článku a můžete si jej prostudovat v dokumentaci. Zde uvedu jen pár příkladů volání této funkce:

// otevře dokument Wordu:
ShellExecute(GetDesktopWindow(), „open“, „c:\data\word\dokument1.doc“,
  NULL, NULL, SW_SHOWNORMAL)

// otevře internetové stránky
ShellExecute(GetDesktopWindow(), „open“, „http://www.rplusj.cz“,
  NULL, NULL, SW_SHOWNORMAL)

// pošle e-mail na danou adresu
ShellExecute(GetDesktopWindow(), „open“, „mailto:radek@rplusj.cz“,
  NULL, NULL, SW_SHOWNORMAL)

V naší komponentě musíme tedy reagovat na kliknutí myší. K tomu použijeme funkci Click(), kterou mají všechny komponenty odvozené od TControl, a kterou tedy můžeme přepsat. Do deklarace třídy přidáme:

…..
protected:
  DYNAMIC void __fastcall Click(void);
…..

Vlastní kód funkce bude vypadat (zatím jednoduše) takto:

void __fastcall TRPJHyperlink::Click()
{
  ShellExecute(GetDesktopWindow(), "open",
    FHyperlink.c_str(), NULL, NULL, SW_SHOWNORMAL);
}

Na závěr

Tím jsme asi naplnili rámec tohoto článku a v příštím díle si komponentu budeme dále dotvářet. Naučíme se při tom, jak zachytávat libovolné zprávy Windows poslané komponentě.

Zde si můžete stáhnout zdrojový kód Hyperlink.zip

Diskuze (3) Další článek: Microsoft odstranil budovy WTC ze svého Flight Simulatoru

Témata článku: , , , , , , , , , , , , , , , , , , , , , , ,