6. Používání grafiky v komponentách

Windows poskytuje GDI (Graphics Device Interface) pro kreslení grafiky nezávislé na zařízení. Naneštěstí GDI má velmi mnoho požadavků na programátora, jako je např. správa grafických zdrojů, což má za následek, že strávíme mnoho času prováděním jiných věcí než tím co skutečně chceme dělat, tj. tvořit grafiku. C++ Builder se za nás stará o GDI, což dovoluje strávit více času produktivní prací a nemusíme hledat ztracená madla nebo neuvolněné zdroje.
C++ Builder se stará o vedlejší úkoly za nás a tak se můžeme zaměřit na produktivní a tvořivou činnost. Funkce GDI můžeme také volat přímo z aplikací C++ Builderu. C++ Builder ale také tyto grafické funkce zaobaluje a umožňuje tak pracovat při vytváření grafiky produktivněji.
Tato kapitola obsahuje:

Úvod do grafiky

C++ Builder obaluje GDI Windows na několika úrovních. Nejdůležitější pro nás jako tvůrce komponent je způsob zobrazení obrazu komponenty na obrazovce. Když voláme funkce GDI přímo, musíme mít madlo kontextu zařízení, ve kterém můžeme vybírat různé kreslící nástroje jako jsou pera, štětce a písma. Po dokreslení našeho grafického obrazu, musíme obnovit kontext zařízení do jeho původního stavu.
Namísto používání této nízké grafické úrovně, C++ Builder poskytuje jednoduché ale kompletní rozhraní: vlastnost Canvas naší komponenty. Plátno přebírá zjišťování zda má přípustný kontext zařízení, a uvolňuje kontext, když již není používán. Plátno má své vlastní vlastnosti reprezentující aktuální pero, štětec a písmo.
Plátno obhospodařuje všechny tyto zdroje za nás, a není nutno se tedy zabývat vytvářením, výběrem a uvolňováním věcí jako je madlo pera. Stačí říci plátnu, který typ pera chceme použít a plátno zajistí zbytek.
Jednou z výhod obhospodařování grafických zdrojů v C++ Builderu je to, že může uschovat zdroje pro pozdější použití, což může značně urychlit opakování operací. Např. jestliže máme program, který opakovaně vytváří, použije a uvolní jistý typ nástroje pera, není stále nutné opakovat tyto kroky. Protože C++ Builder uchovává grafické zdroje, opakovaně použije již existující nástroj pera.
Jako příklad toho, jak jednoduchý grafický kód C++ Builderu může být, ukazují následující dvě ukázky kódu. První používá standardní funkce GDI k nakreslení žluté elipsy modře orámované na okně v aplikaci zapsané v ObjectWindows. Druhá používá plátno k nakreslení stejné elipsy v aplikaci zapsané v C++ Builderu.
void TMojeOkno::Paint(TDC& PaintDC, bool erase, TRect& rect)
{
  HPEN PenHandle, OldPenHandle;
  HBRUSH BrushHandle, OldBrushHandle;
  PenHandle=CreatePen(PS_SOLID, 1, RGB(0,0,255)); // vytvoření modrého pera
  OldPenHandle=SelectObject(PaintDc,PenHandle);   // říká DC aby jej používal
  BrushHandle=CreateSolidBrush(RGB(255,255,0));   // vytvoření žlutého štětce
  OldBrushHandle=SelectObject(PaintDC,BrushHandle); //říká DC aby jej používal
  Ellipse(HDC, 10, 10, 50, 50);                   // nakreslení elipsy
  SelectObject(OldBrushHandle);                   // obnovení původního štětce
  DeleteObject(BrushHandle);                      // zrušení žlutého štětce
  SelectObject(OldPenHandle);                     // obnovení původního pera
  DeleteObject(PenHandle);                        // zrušení modrého pera
}
Kód C++ Builderu, který provádí totéž:
void __fastcall TForm1::FormPaint(TObject *Sender)
{
  Canvas->Pen->Color = clBlue;      // udělání modrého pera
  Canvas->Brush->Color = clYellow;  // udělání žlutého štětce
  Canvas->Ellipse(10, 10, 50, 50);  // nakreslení elipsy
}

Používání plátna

Objekt plátna obaluje grafiku Windows na několika úrovních. Vysoká úroveň obsahuje funkce pro kreslení čar, tvarů a textů, prostřední úroveň pro manipulaci s kreslícími možnostmi plátna a nízká úroveň pro přístup k GDI Windows. Tyto možnosti jsou uvedeny v následující tabulce:
 
Úroveň Operace Nástroje
Vysoká Kreslení čar a tvarů
Zobrazení a umístění textu
Vyplňování oblastí
Metody jako MoveTo, LineTo, Rectangle a Ellipse
Metody TextOut, TextHight, TextWidth a TextRect
Metody FillRect a FloodFill
Střední Přizpůsobení textu a grafiky
Manipulace s body
Kopírování a spojování obrázků
Vlastnosti Pen, Brush a Font
Vlastnost Pixels
Metody Draw, StretchDraw, BrushCopy a CopyRect a vlastnost CopyMode
Nízká Volání funkcí GDI Windows Vlastnost Handle

Práce s obrázky

Mnoho práce v C++ Builderu je omezeno na kreslení přímo na plátno komponent a formulářů. C++ Builder také umožňuje zpracovávat standardní obrázky jako jsou bitové mapy, metasoubory a ikony, včetně automatické správy palet.
V C++ Builderu jsou tři důležité aspekty práce s obrázky:

Používání obrázků, grafiky a pláten

V C++ Builderu jsou tři typy objektů určených pro práci s grafikou: Objekt obrázku má vždy grafiku a grafika má plátno (plátno má pouze TBitmap). Normálně, když pracujeme s obrázkem, pak pracujeme pouze s částí grafického objektu přístupného prostřednictvím TPicture. Jestliže požadujeme přístup ke specifikám samotného grafického objektu, můžeme se odkázat na vlastnost Graphic obrázku.

Zavádění a ukládání grafiky

Všechny obrázky a grafika v C++ Builderu mohou zavádět svůj obraz ze souboru a ukládat jej opět zpět (nebo do jiných souborů). Můžeme zavádět a ukládat obraz obrázků kdykoli. K zavedení obrazu obrázku ze souboru voláme metodu LoadFromFile obrázku a k uložení obrazu metodu SaveToFile. LoadFromFile a SaveToFile přebírají jméno souboru jako parametr. LoadFromFile používá příponu souboru k určení, který typ grafického objektu má být vytvořen a zaveden. SaveToFile ukládá typ souboru určený typem ukládaného grafického objektu.
K zavedení bitové mapy do ovladače Image, předáme jméno souboru bitové mapy metodě LoadFromFile obrázku:
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  Image1->Picture->LoadFromFile("XYZ.BMP");
}
Obrázek používající BMP jako standardní příponu pro soubory bitových map, vytváří svou grafiku jako TBitmap a pak volají metodu LoadFromFile obrázku. Jelikož grafika je bitová mapa, je zaveden její obraz ze souboru jako bitová mapa.

Obsluhování palet

Když pracujeme na zařízení založené na paletě, pak C++ Builder automaticky řídí podporu realizace palety. Tj. jestliže máme ovladač, který má paletu, můžeme používat dvě metody zděděné od TControl k řízení jak Windows přizpůsobí tuto paletu.
Podpora palety pro ovladače má tyto dva aspekty: specifikace palety pro ovladač a reakce na změny palety. Mnoho ovladačů paletu nepoužívá. Nicméně ovladače, které obsahují grafiku (jako je např. komponenta Image) ji mít musí k interakci s Windows a ovladači řízení obrazovky k zajištění odpovídajícího vzhledu ovladače. Windows se odkazuje na tento proces jako na realizaci palet.
Realizace palet je proces zajišťující, že nejvrchnější okno používá svou plnou paletu a spodní okna používají z této palety to co je možné a ostatní barvy mapují na barvy v ?reálné paletě. Když se okno přesune nad jiné, Windows stá? paletě. Když
Poznámka: C++ Builder sám neposkytuje specifickou podporu pro vytváření nebo obhospodařování palet, jiných než bitových map.
Jestliže máme paletu, kterou chceme použít na ovladači, musíme říci naši aplikaci aby použila tuto paletu. Pro specifikaci palety pro ovladač, předefinujeme metodu GetPalette ovladače, aby vracela madlo požadované palety. Specifikací palety pro ovladač provedeme v naši aplikaci dvě věci: řekneme aplikaci, že paleta našeho ovladače má být realizována a určíme paletu použitou pro realizaci.
Jestliže náš ovladač specifikuje paletu předefinováním GetPalette, C++ Builder automaticky přebírá odpovědnost za reakce na zprávy palety od Windows. Metoda provádějící správu palet je PaletteChanged.
Pro normální operace, nepotřebujeme nikdy změnit její implicitní chování. Primární úloha PaletteChanged je v určení, zda realizovaná paleta ovladače je na popředí nebo na pozadí. Windows ovládá tuto realizaci palet tím, že nejvrchnější okno má paletu popředí a ostatní okna získají palety pozadí. C++ Builder provádí jeden krok navíc, realizuje palety pro ovladače v Tab pořadí ovladačů. Toto implicitní chování můžeme chtít předefinovat pouze, když chceme aby ovladač, který není první v Tab pořadí, získal paletu popředí.

Neobrazovkové bitové mapy

Když kreslíme složitější obrázek obecnou technikou v programování Windows, vytvoříme neobrazovkovou bitovou mapu, nakreslíme na ní obrázek a potom překopírujeme kompletní obraz na definitivní místo na obrazovce. Použití obrázku neobrazovkové bitové mapy omezuje kmitání způsobené opakovaným kreslením přímo na obrazovku. Objekty bitových map v C++ Builderu reprezentující bitově mapované obrázky ve zdrojích a souborech mohou také pracovat jako obrázky neobrazovkových bitových map. Při práci s neobrazovkovými bitovými mapami jsou dva hlavní aspekty:

Vytváření a údržba neobrazovkových bitových map

Když vytváříme složitější grafický obrázek, nemusíme obecně kreslit přímo na plátno, které je zobrazováno na obrazovce. Namísto kreslení na plátno formuláře nebo ovladače, můžeme vytvořit objekt bitové mapy, kreslit na jeho plátno a potom překopírovat kompletní obraz na obrazovku. Na příklad kreslení složitějšího obrázku na neobrazovkovou bitovou mapu se můžeme podívat do zdrojového kódu ovladače Gauge ze stránky Samples Palety komponent. Tento ovladač kreslí různé tvary a texty na neobrazovkovou bitovou mapu před překopírováním na obrazovku. Zdrojový kód nalezneme v souboru CGauges.cpp v adresáři Program files\Borland\CBuilder\Examples\Controls\Source.

Kopírování obrázků bitových map

C++ Builder poskytuje čtyři různé způsoby kopírování obrázků z jednoho plátna na jiné. V závislosti na požadovaném efektu voláme různé metody:
 
Vytváří efekt Voláme metodu
Kopírování celé grafiky  Draw
Kopírování a změna velikosti  StretchDraw
Kopírování části plátna CopyRect
Kopírování bitových map s rastrovou operací BrushCopy

Reagování na změny

Všechny grafické objekty, včetně pláten a jejich vlastních objektů (per, štětců a písem) mají události reakcí na změny v objektu. Použitím těchto událostí, můžeme umožnit naši komponentě (nebo aplikaci, která ji používá) reagovat na změny překreslením svých obrazů. Pro reakci na změny v grafickém objektu, přiřadíme metodu události OnChange objektu.
Komponenta TShape zveřejňuje vlastnosti reprezentující pero a štětec používané k zobrazení svého tvaru. Konstruktor komponenty přiřadí metodu k události OnChange každé z nich, což způsobuje, že komponenta překresluje svůj obraz při změnách pera nebo štětce. I když komponenta TShape je zapsána v Object Pascalu, při zápisu v C++ by mohla být definována takto (nejprve je uveden výpis hlavičkového souboru):
class PACKAGE TMyShape : public TGraphicControl
{
private:
protected:
public:
    virtual __fastcall TMyShape(TComponent* Owner);
__published:
    TPen *FPen;
    TBrush *FBrush;
    void __fastcall StyleChanged(TObject *Sender);
};
Toto je kód CPP souboru:
__fastcall TMyShape::TMyShape(TComponent* Owner)
  : TGraphicControl(Owner)
{
  Width = 65;
  Height = 65;
  FPen = new TPen;
  FPen->OnChange = StyleChanged;
  FBrush = new TBrush;
  FBrush->OnChange = StyleChanged;
}
void __fastcall TMyShape::StyleChanged(TObject *Sender)
{
  Invalidate();
}

  1. Jednoduchým typem komponenty je grafický ovladač. Protože grafický ovladač nikdy nemůže získat vstupní zaostření, nemá a nepotřebuje madlo okna. Uživatel aplikace obsahující grafické ovladače může stále manipulovat s ovladači pomocí myši, ale nemůže použít klávesnici. Grafická komponenta vytvářená v tomto bodě odpovídá komponentě TShape ze stránky Additional Palety komponent. Přestože vytvářená komponenta je identická, je nutno ji nazvat jinak, aby nedošlo k duplikaci identifikátorů. Naši komponentu nazveme TSampleShape. Vytvoření grafické komponenty vyžaduje tři kroky: vytvoření a registraci komponenty, zveřejnění zděděných vlastností a přidání grafických možností.

  2. Vytváření každé komponenty začíná vždy stejně. V našem příkladě použijeme manuální vytváření s těmito specifikami: jednotku komponenty nazveme Shapes, odvodíme nový typ komponenty TSampleShape od TGraphicControl a registrujeme TSampleShape na stránce Samples Palety komponent. Výsledkem této práce je (nejprve je uveden hlavičkový soubor):
    #ifndef ShapesH
    #define ShapesH
    #include <vcl\sysutils.hpp>
    #include <vcl\controls.hpp>
    #include <vcl\classes.hpp>
    #include <vcl\forms.hpp>
    class PACKAGE TSampleShape : public TGraphicControl
    {
    private:
    protected:
    public:
    __published:
    };
    #endif
    CPP soubor vypadá takto:
    #include <vcl\vcl.h>
    #pragma hdrstop
    #include "Shapes.h"
    #pragma package(smart_init);
    static inline TSampleShape *ValidCtrCheck()
    {
      return new TSampleShape(NULL);
    }
    namespace Shapes
    {
      void __fastcall PACKAGE Register()
      {
        TComponentClass classes[1] = {__classid(TSampleShape)};
        RegisterComponents("Samples", classes, 0);
      }
    }
  3. Když odvodíme typ komponenty, můžeme určit, které vlastnosti a události deklarované v chráněné části typu předka chceme zpřístupnit pro uživatele naši nové komponenty. Potomci TGraphicControl vždy zveřejňují všechny vlastnosti, které umožňují komponentě aby pracovala jako ovladač, tj. musíme zveřejnit všechny reakce na události myši a obsluhu tažení. Zveřejňování zděděných vlastností a událostí spočívá v opětovné deklaraci jména vlastnosti ve zveřejňované části deklarace typu objektu. V našem příkladě zveřejníme tři události myši, tři události tažení a dvě vlastnosti tažení:

  4. class PACKAGE TSampleShape : public TGraphicControl
    {
    __published:
      __property DragCursor;
      __property DragMode;
      __property OnDragDrop;
      __property OnDragOver;
      __property OnEndDrag;
      __property OnMouseDown;
      __property OnMouseMove;
      __property OnMouseUp;
    };
    Tím ovladač zpřístupní pro své uživatele práci s myší a tažení.
  5. Když máme deklarovanou svou grafickou komponentu a zveřejněné některé potřebné zděděné vlastnosti, můžeme naši komponentě přidat požadované grafické možnosti. Při vytváření grafického ovladače vždy provádíme tyto dva kroky: určíme co kreslit a nakreslíme obraz komponenty. Dále pro náš příklad musíme přidat některé vlastnosti, které umožní vývojáři aplikace použít náš ovladač k přizpůsobení vzhledu při návrhu.

  6. Grafický ovladač má obecně možnost měnit svůj vzhled v reakci na dynamické nebo uživatelem specifikované podmínky. Grafická komponenta, která je stále stejná může být tvořena importovaným obrazem bitové mapy a nemusí to být grafický ovladač. Obecně, vzhled grafického ovladače závisí na některých kombinacích hodnot svých vlastností. Např. ovladač Gauge má vlastnosti které určují jeho tvar. Podobně ovladač Shape má vlastnost, která určuje jaký typ tvaru bude kreslen.
    Pro přidáni možnosti ovladači Shape určit kreslený tvar, přidáme vlastnost nazvanou Shape, což provedeme v těchto třech krocích: deklarujeme typ vlastnosti, deklarujeme vlastnost a zapíšeme implementaci metody.
    Když deklarujeme vlastnost uživatelem definovaného typu, musíme nejprve deklarovat typ vlastnosti a teprve potom třídu, která obsahuje vlastnost. Většina uživatelem definovaných typů pro vlastnosti jsou výčtové typy. Pro ovladač Shape, použijeme výčtový typ s prvky pro každý typ tvaru, který ovladač může kreslit. Přidáme následující definici typu před deklaraci třídy Shape:
    enum TSampleShapeType { sstRectangle, sstSquare, sstRoundRect,
                            sstRoundSquare, sstEllipse, sstCircle };
    class PACKAGE TSampleShape : public TGraphicControl
    Nyní můžeme tento typ použít k deklarování nové vlastnosti v objektu.
    Když deklarujeme vlastnost, obvykle potřebujeme deklarovat soukromou položku k uložení dat vlastnosti, a specifikujeme přístupové metody vlastnosti (čtení hodnoty provádíme často přímo). Pro náš ovladač deklarujeme položku pro uložení aktuálního tvaru a deklarujeme vlastnost, která čte tuto položku a zapisuje ji prostřednictvím volání metody.
    Přidáme tedy do deklarace typu TSampleShape toto:
    class PACKAGE TSampleShape : public TGraphicControl
    {
    private:
      TSampleShapeType FShape;
      void __fastcall SetShape(TSampleShapeType Value);
    __published:
      __property TSampleShapeType Shape={read=FShape,write=SetShape,nodefault};
    };
    Nyní ještě musíme implementovat metodu SetShape. Do souboru CPP jednotky přidáme:
    void __fastcall TSampleShape::SetShape(TSampleShapeType Value)
    {
      if (FShape != Value){
        FShape = Value;
        Invalidate();
      }
    }
    K umožnění změny implicitních vlastností a inicializaci vlastněných objektů pro naši komponentu musíme předefinovat zděděný konstruktor a destruktor. V obou nesmíme zapomenou volat zděděnou metodu. Implicitní velikost grafického ovladače je malá a tak změníme šířku a výšku v konstruktoru. V našem příkladě nastavíme velikost obou rozměrů na 65 bodů. Do deklarace třídy komponenty přidáme přepsání konstruktoru:
    class PACKAGE TSampleShape : public TGraphicControl
    {
    public:
      virtual __fastcall TSampleShape(TComponent* Owner);
    }
    opětovně deklarujeme vlastnosti Height a Width s jejich novými implicitními hodnotami:
    class PACKAGE TSampleShape : public TGraphicControl
    {
    __published:
      __property Height = {default=65};
      __property Width = {default=65};
    };
    a do souboru CPP zapíšeme implementaci nového konstruktoru:
    __fastcall TSampleShape::TSampleShape(TComponent* Owner)
     : TGraphicControl(Owner)
    {
      Width = 65;
      Height = 65;
    }
  7. Implicitně plátno má tenké černé pero a plný bílý štětec. Pro povolení změny těchto prvků plátna vývojáři používajícího ovladač Shape, musíme poskytnout třídy pro manipulaci s nimi při návrhu, a potom kopírovat tyto třídy na plátno, když kreslíme. Třídy jako je pero nebo štětec se nazývají vlastněné třídy, protože komponenta je vlastní a je zodpovědná za jejich vytvoření a zrušení. Správa vlastněných tříd vyžaduje čtyři kroky: deklaraci datových složek třídy, deklaraci přístupových vlastností, inicializaci vlastněných objektů a nastavování vlastností vlastněných tříd

  8. Každý objekt vlastněný komponentou musí mít objektovou položku deklarovanou v komponentě. Položka zajišťuje, že komponenta má vždy ukazatel na vlastněný objekt a může tak před svým zrušením objekt zrušit. Obecně komponenta inicializuje vlastněné objekty ve svém konstruktoru a ruší ve svém destruktoru. Položky pro vlastněné objekty jsou téměř vždy deklarovány jako soukromé. Jestliže uživatel komponenty požaduje přístup k vlastněným objektům, musí deklarovat vlastnosti, které poskytují přístup. Přidáme položky pro objekty pera a štětce k naší třídě:
    class PACKAGE TSampleShape : public TGraphicControl
    {
    private:
      TPen *FPen;
      TBrush *FBrush;
    };
    K vlastněným objektům komponenty můžeme poskytnout přístup deklarací vlastností typu objektů. To dává vývojáři možnost přístupu k objektům jednak během návrhu, tak i při běhu aplikace. Obecně čtecí část vlastnosti je odkaz na položku objektu, ale zápisová část volá metodu, která povoluje komponentě reagovat na změny ve vlastněném objektu. V naši komponentě přidáme vlastnosti, které poskytují přístup k položkám pera a štětce. Musíme také deklarovat metody provádějící změny pera a štětce.
    class PACKAGE TSampleShape : public TGraphicControl
    {
    private:
      TPen *FPen;
      TBrush *FBrush;
      void __fastcall SetBrush(TBrush *Value);
      void __fastcall SetPen(TPen *Value);
    __published:
      __property TBrush* Brush={read=FBrush,write=SetBrush,nodefault};
      __property TPen* Pen={read=FPen,write=SetPen,nodefault};
    };
    Potom do souboru CPP jednotky zapíšeme metody SetBrush a SetPen:
    void __fastcall TSampleShape::SetBrush(TBrush* Value)
    {
      FBrush->Assign(Value);
    }
    void __fastcall TSampleShape::SetPen(TPen* Value)
    {
      FPen->Assign(Value);
    }
    Pokud bychom použili přímé přiřazení Value do FBrush (nebo FPen):
    FBrush = Value;
    pak přepíšeme interní ukazatel na FBrush, ztratíme paměť a vytvoříme si řadu dalších problémů.
    Jestliže k naši komponentě přidáme objekty, konstruktor komponenty musí tyto objekty inicializovat a tak umožnit uživateli pracovat s objekty při běhu aplikace. Podobně destruktor komponenty musí také uvolnit vlastněné objekty před uvolněním komponenty samotné. V konstruktoru ovladače Shape vytvoříme pero a štětec
    __fastcall TSampleShape::TSampleShape(TComponent* Owner)
     : TGraphicControl(Owner)
    {
      Width = 65;
      Height = 65;
      FBrush = new TBrush();
      FPen = new TPen();
    }
    přidáme přepsaný destruktor do deklarace objektu komponenty
    class PACKAGE TSampleShape : public TGraphicControl
    {
    public:
      virtual __fastcall TSampleShape(TComponent* Owner);
      __fastcall ~TSampleShape();
    };
    a do souboru CPP zapíšeme nový destruktor:
    __fastcall TSampleShape::~TSampleShape()
    {
      delete FPen;
      delete FBrush;
    }
    Jako poslední krok ve zpracování objektů pera a štětce, musíme zajistit, že změny v peru a štětci způsobí překreslení samotného ovladače Shape. Objekty pera i štětce mají události OnChange a můžeme tedy v ovladači Shape vytvořit metodu a přiřadit ji oběma událostem OnChance. V našem kódu se změní:
    class PACKAGE TSampleShape : public TGraphicControl
    {
    public:
      void __fastcall StyleChanged(TObject* Owner);
    };
    V CPP souboru přiřadíme metodu StyleChanged události OnChange pro třídy pera a štětce v konstruktoru TSampleShape. Vložíme také implementaci metody StyleChanged:
    __fastcall TSampleShape::TSampleShape(TComponent* Owner)
     : TGraphicControl(Owner)
    {
      Width = 65;
      Height = 65;
      FBrush = new TBrush();
      FBrush->OnChange = StyleChanged;
      FPen = new TPen();
      FPen->OnChange = StyleChanged;
    }
    void __fastcall TSampleShape::StyleChanged(TObject* Owner)
    {
      Invalidate();
    }
    Po provedení těchto změn se komponenta v reakci na změnu pera nebo štětce překreslí.
    Základní schopností grafického ovladače je schopnost nakreslení svého obrazu na obrazovku. Abstraktní typ TGraphicControl definuje virtuální metodu nazvanou Paint, kterou přepíšeme k nakreslení požadovaného obrazu naší komponenty. Metoda Paint pro komponentu Shape musí provést několik věcí: použít pero a štětec vybraný uživatelem, použit vybraný tvar a upravit souřadnice tak, aby čtverec a kruh měli stejnou šířku a výšku. Přepsání metody Paint vyžaduje přidat Paint do deklarace komponenty a do souboru CPP zapsat metodu Paint. Po provedení těchto věcí dostaneme:
    class PACKAGE TSampleShape : public TGraphicControl
    {
    protected:
      virtual void __fastcall Paint();
    };

    void __fastcall TSampleShape::Paint()
    {
      int X, Y, W, H, S;
      Canvas->Pen = FPen;
      Canvas->Brush = FBrush;
      W = Width;
      H = Height;
      X = Y = 0;
      if (W < H) S = W;
      else S = H;
      switch (FShape) {
        case sstSquare:
        case sstRoundSquare:
        case sstCircle:
          X = (W - S)/2;
          Y = (H - S)/2;
          break;
        default:
          break;
      }
      switch (FShape) {
        case sstSquare:
          W = H = S;
        case sstRectangle:
          Canvas->Rectangle(X,Y,X+W,Y+H);
          break;
        case sstRoundSquare:
          W = H = S;
        case sstRoundRect:
          Canvas->RoundRect(X,Y,X+W,Y+H,S/4,S/4);
          break;
        case sstCircle:
          W = H = S;
        case sstEllipse:
          Canvas->Ellipse(X,Y,X+W,Y+H);
          break;
        default:
          break;
      }
    }
    Tím je vývoj této komponenty dokončen.

  9. Dále se pokusíme vytvořit komponentu digitálních hodin. Protože budeme provádět určitý textový výstup, můžeme odvození provést od komponenty Label. V tomto případě může ale uživatel měnit titulek komponenty. Abychom tomu zabránili, použijeme jako rodičovskou třídu komponentu TCustomLabel, která má stejné schopnosti, ale méně zveřejňovaných vlastností (můžeme sami určit, které vlastnosti zveřejníme). Kromě předeklarování některých vlastností třídy předka bude mít naše komponenta (TDigClock) jednu vlastní a to vlastnost Active. Tato vlastnost udává, zda hodiny pracují či ne. Komponenta hodin bude uvnitř obsahovat komponentu Timer, která ji nutí pracovat. Časovač ale není zveřejněn přes vlastnost, protože nechceme aby byl přístupný. Pouze jeho vlastnost Enabled je zaobalena do naši vlastnosti Active. Následuje výpis programové jednotky nové komponenty (nejprve je uveden výpis hlavičkového souboru):

  10. #ifndef DigClockH
    #define DigClockH
    #include <SysUtils.hpp>
    #include <Controls.hpp>
    #include <Classes.hpp>
    #include <Forms.hpp>
    #include <StdCtrls.hpp>
    class PACKAGE TDigClock : public TCustomLabel
    {
    private:
      TTimer *FTimer;
      bool __fastcall GetActive();
      void __fastcall SetActive(bool Value);
    protected:
      void __fastcall UpdateClock(TObject *Sender);
    public:
      __fastcall TDigClock(TComponent* Owner);
      __fastcall ~TDigClock();
    __published:
      __property Align;
      __property Alignment;
      __property Color;
      __property Font;
      __property ParentColor;
      __property ParentFont;
      __property ParentShowHint;
      __property PopupMenu;
      __property ShowHint;
      __property Transparent;
      __property Visible;
      __property bool Active = {read=GetActive, write=SetActive, nodefault};
    };
    #endif

    #include <vcl.h>
    #pragma hdrstop
    #include "DigClock.h"
    #pragma package(smart_init);
    __fastcall TDigClock::TDigClock(TComponent* Owner) : TCustomLabel(Owner)
    {
      TComponentClass classes[1] = {__classid(TTimer)};
      RegisterClasses(classes, 0);
      FTimer = new TTimer(Owner);
      FTimer->OnTimer = UpdateClock;
      FTimer->Enabled = true;
    }
    __fastcall TDigClock::~TDigClock()
    {
      delete FTimer;
    }
    void __fastcall TDigClock::UpdateClock(TObject *Sender)
    {
      Caption = TimeToStr(Time());
    }
    bool __fastcall TDigClock::GetActive()
    {
      return FTimer->Enabled;
    }
    void __fastcall TDigClock::SetActive(bool Value)
    {
      FTimer->Enabled = Value;
    }
    namespace Digclock
    {
      void __fastcall PACKAGE Register()
      {
        TComponentClass classes[1] = {__classid(TDigClock)};
        RegisterComponents("Samples", classes, 0);
      }
    }
    Prostudujte si uvedený výpis. Před vytvořením objektu Timer je požadována registrace typu této komponenty, která je naší komponentou používána. Jinak by vám měl výpis být srozumitelný. Vyzkoušejte použití této komponenty v nějaké aplikaci.

  11. Vytvořte komponentu analogových hodin.
6. Používání grafiky v komponentách