Ú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 |
-
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í.
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);
}
}
-
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í:
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í.
-
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.
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;
}
-
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
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.
-
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):
#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.
-
Vytvořte komponentu analogových hodin.