CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
DOCUMENTE SIMILARE |
|
TERMENI importanti pentru acest document |
|
Jednou z klíčových možností Builderu je možnost rozšiřování knihovny komponent. Všechny komponenty Builderu patří do objektové hierarchie nazvané knihovna vizuálních komponent (VCL). Na následujícím obrázku je uvedena zjednodušená hierarchie objektů tvořících VCL.
TObject
Exception TStream TPersistent TPrinter TList
TGraphicObject TGraphic TComponent TCanvas TPicture TString
TTimer TScreen TMenuItem TMenu TControl TCommanDialog TGlobalComponent
TGraphicControl TWinControl TApplication
TCustomComboBox TButtonControl
TCustomControl TCsrollBar
TCustomEdit TScrollingWinControl
TCustomListBox
TForm
Typ TComponent je společný předek všech komponent VCL. TComponent poskytuje minimální vlastnosti a události nezbytné pro práci komponenty v Builderu. Různé větve knihovny poskytují další více specializované komponenty. Vytvářenou komponentu přidáme do VCL odvozením nového objektu od některého existujícího typu objektu v hierarchii. Komponenty jsou objekty a tvůrce komponent pracuje s objekty na jiné úrovni než uživatel komponent. Vytvoření nové komponenty vyžaduje odvození nového typu objektu. Jsou dva hlavní rozdíly mezi vytvářením komponent a používáním komponent. Při vytváření komponent musíme mít přístup k částem objektu, které jsou nepřístupné pro koncového uživatele a můžeme přidávat nové části (např. vlastnosti) ke svým komponentám. S ohledem na tyto rozdíly, je nutno dodržovat mnoho konvencí a brát ohled na používání našich komponent koncovými uživateli.
Komponenty jsou prvky programu s nimiž manipulujeme během návrhu. Vytváření nové komponenty znamená odvození typu objektu nové komponenty od existujícího typu. V následující tabulce jsou uvedeny různé typy komponent a typy objektů od kterých je odvozujeme.
K provedení |
Začneme od typu |
modifikace existující komponenty |
libovolné existující komponenty, jako je TButton nebo TListBox, nebo od typu abstraktní komponenty jako je TCustomListBox |
Vytváření původního ovladače |
TCustomControl |
Vytváření grafického ovladače |
TGraphicControl |
Použití existujícího ovladače Windows |
TWinControl |
Vytváření nevizuální komponenty |
TComponent |
Můžeme také odvodit jiné objekty, které nejsou komponentami, ale nemůžeme s nimi manipulovat na formuláři. Builder obsahuje několik těchto typů objektů, jako je TINIFile nebo TFont.
Nejjednodušší způsob vytvoření komponenty je začít od existující funkční komponenty a přizpůsobit ji. Novou komponentu můžeme odvodit od libovolné komponenty poskytnuté Builderem. Např. můžeme chtít změnit hodnotu implicitní vlastnosti jednoho ze standardních ovladačů.
Některé ovladače, jako jsou komponenty seznamu a mřížky, mají mnoho variant na základní téma. V těchto případech Builder poskytuje abstraktní typ ovladače (se slovem “Custom” ve svém jméně, jako je např. TCustomGrid), který slouží k odvozování jednotlivých verzí. Např. můžeme potřebovat vytvořit speciální typ seznamu, který nemá některé vlastnosti standardního typu TListBox. Jelikož nelze odstranit vlastnost z typu předka, musíme svou komponentu odvodit od něčeho výše v hierarchii než je TListBox. Nemusíme začít od typu prázdného abstraktního ovladače a vytvářet všechny funkce seznamu, neboť VCL poskytuje TCustomListBox, který implementuje všechny vlastnosti potřebné pro seznam, ale všechny je nezveřejňuje. Když odvozujeme komponentu od některého z abstraktních typů, jako je TCustomListBox, zveřejníme ty vlastnosti, které chceme zpřístupnit ve své komponentě a ostatní ponecháme chráněné.
Při vytváření původního (nového) ovladače je důležité toto. Standardní ovladač je prvek, který je viditelný při běhu aplikace a obvykle s ním uživatel může pracovat. Tyto standardní ovladače jsou všechny potomky objektu TCustomControl. Když vytváříme původní ovladač (takový, který není svázán s žádným existujícím ovladačem), musíme jako počáteční bod použít TCustomControl. Klíčovým aspektem standardního ovladače je to, že má madlo okna, uložené ve vlastnosti nazvané Handle. Madlo okna umožňuje Windows “vědět o” ovladači a mimo jiné umožňuje ovladači získat vstupní zaostření a předávat madlo funkcím Windows API (Windows potřebuje madlo k určení okna, se kterým má operovat). Jestliže náš ovladač nepotřebuje získat vstupní zaostření, můžeme jej vytvořit jako grafický ovladač, který nevyužívá systém zdrojů Windows. Všechny ovladače, které reprezentují standardní ovladače Windows, jako jsou tlačítka, seznamy a editační okna jsou potomky TWinControl (mimo TLabel, neboť tento ovladač nemůže získat vstupní zaostření).
Grafické ovladače jsou velmi podobné uživatelským ovladačům, ale nejsou odvozeny od ovladačů Windows, tj. Windows nic o grafických ovladačích neví. Nemají madlo okna a tedy nečerpají zdroje systému. Hlavním omezením grafických ovladačů je, že nemohou získat vstupní zaostření. Builder podporuje vytváření grafických uživatelských ovladačů prostřednictvím typu TGraphicControl. TGraphicControl je abstraktní typ odvozený od TControl. Přestože můžeme odvozovat ovladače od TControl, je vhodnější je odvozovat od třídy TGraphicControl, která poskytuje plátno na kreslení a zpracovává zprávu WM_PAINT a není tedy nutné přepisovat metodu Paint.
Windows má koncepci nazvanou třída okna, která se podobá koncepci objektově orientovaného programování objektů nebo tříd. Třída okna je množina informací sdílená mezi různými instancemi stejného typu oken nebo ovladačů ve Windows. Když vytváříme nový typ ovladače (obvykle nazývaný uživatelský ovladač) v tradičním programování Windows, definujeme novou třídu okna a registrujeme ji ve Windows. Můžeme také založit novou třídu okna na existující třídě. Jestliže chceme vytvořit uživatelský ovladač v tradičním programování Windows, musíme jej zapsat v DLL stejně jako standardní ovladače Windows a poskytnout k němu rozhraní. Pomocí Delphi můžeme vytvořit komponentu “obálkou” okolo existující třídy Windows. Jestliže tedy máme knihovnu uživatelských ovladačů, které chceme používat ve svých aplikacích Delphi, můžeme vytvořit komponenty Delphi, ve kterých použijeme existující ovladače a odvodíme od nich nové ovladače a to stejně jako od jiných komponent. I když v této publikaci není uveden žádný příklad použití existujícího ovladače Windows, můžeme se s touto technikou seznámit v komponentách programové jednotky StdCtls, které reprezentují standardní ovladače Windows, např. TEdit.
Vytváření nevizuálních komponent. Abstraktní objekt TComponent je základním typem pro všechny komponenty. Nevizuální komponenty jsou pouze komponenty, které vytváříme přímo od TComponent. TComponent definuje všechny nezbytné vlastnosti a metody komponenty pro spolupráci s Návrhářem formuláře. Tedy libovolné komponenty odvozené od TComponent mají zabudované možnosti návrhu. Nevizuální komponenty jsou používány málo. Jejich využití je jako rozhraní pro nevizuální prvky programu (např. pro databázové prvky) a držení místa pro dialogová okna (např. souborová dialogová okna).
Je několik omezení na to, co můžeme vložit do komponenty. Nicméně, jsou jisté konvence, které je vhodné dodržovat, jestliže chceme udělat komponentu spolehlivou a snadno použitelnou pro její uživatele. Snad nejdůležitější princip tvorby komponent Builderu je nezbytnost odstranit závislosti. Jedna z věcí, která zjednodušuje použití komponent pro koncové uživatele v aplikaci, je skutečnost, že zde nejsou obecně žádná omezení na to, co s nimi můžeme udělat na jakémkoli daném místě svého kódu. Povaha komponent, předpokládá, že různí uživatelé je použijí ve svých aplikacích v rozličných kombinacích, pořadích a prostředcích. Měli bychom navrhovat své komponenty, aby jejich funkčnost v jakémkoli kontextu nebyla ničím podmíněna. Vytvořit komponenty, které nejsou závislé, možná zabere více času, je to ale vhodně využitý čas.
Kromě viditelného obrazu komponenty, se kterým uživatel manipuluje na formuláři při návrhu, jsou nejdůležitější atributy komponenty její vlastnosti, události a metody. Vlastnosti dávají uživatelům komponent iluzi nastavování nebo čtení hodnot proměnných v komponentě, zatímco tvůrce komponenty skrývá použité datové struktury a implementaci přístupu k hodnotám. Používání vlastností dává tyto výhody: Vlastnosti jsou přístupné během návrhu (to umožňuje uživatelům komponent nastavovat a měnit počáteční hodnoty vlastností bez zásahu do kódu), vlastnosti mohou testovat hodnoty nebo formáty, které jim uživatel přiřadí (kontrolou uživatelova vstupu zabraňujeme chybám způsobeným nedovolenými hodnotami) a komponenta může na žádost vytvářet příslušnou hodnotu (častý typ programátorských chyb je odkaz na proměnnou, která nemá přiřazenou počáteční hodnotu; vytvořením hodnoty vlastnosti, můžeme zajistit, že hodnota vlastnosti je vždy přípustná). Události jsou propojením mezi výskyty určenými tvůrcem komponenty (např. akce myši nebo klávesnice) a kódem zapsaným uživatelem komponenty (obsluha události). Událost je možnost poskytnutá tvůrcem komponenty uživateli komponenty pro specifikaci kódu, který má být proveden v určitých případech. Uživatel komponenty může specifikovat obsluhu pro předdefinovanou událost a nemusí odvozovat vlastní komponentu. Metody jsou funkce zabudované v komponentě. Uživatel komponenty používá metody k přikázání komponentě, aby provedla specifickou akci nebo vrátila jistou hodnotu, která není vlastností. Metody jsou také užitečné pro aktualizaci několika svázaných vlastností jediným voláním. Protože metody vyžadují provedení kódu jsou dostupné pouze za běhu programu.
Builder odstraňuje namáhavou práci s grafikou Windows zaobalením různých grafických nástrojů do plátna. Plátno reprezentuje kreslící plochu okna nebo ovladače a obsahuje další objekty jako je pero, štětec a písmo. Plátno je něco jako kontext zařízení Windows, ale přebírá starost o řadu věcí za nás. Jestliže vytváříme grafickou aplikaci Windows, musíme se seznámit s typy požadavků grafického rozhraní Windows, jako jsou limity příslušných kontextů zařízení a obnovováním grafických objektů na jejich počáteční stav před jejich uvolněním. Když pracujeme s grafikou v Builderu, nemusíme tyto věci znát. Pro kreslení na formulář nebo komponentu, přistupujeme k vlastnosti Canvas. Jestliže chceme přizpůsobit pero nebo štětec, nastavíme barvu nebo styl. Když skončíme, Builder přebírá starost za uvolnění zdrojů. Máme stále plný přístup k GDI Windows, ale náš kód bude jednodušší a bude pracovat rychleji, jestliže použijeme plátno zabudované do komponent Builderu.
Dříve než můžeme komponentu použít při návrhu, musíme ji v Builderu registrovat. Registrace říká Builderu, na které stránce Palety komponent chceme komponentu zobrazit.
Při vytváření nové komponenty musíme provést několik kroků. Komponentu lze vytvořit dvěma způsoby: ručním vytvářením komponenty nebo pomocí Experta komponent. Ať již použijeme kterýkoli způsob, musíme komponentu, která má alespoň minimální funkčnost instalovat na Paletu komponent. Potom lze komponentě přidávat další funkce, aktualizovat paletu a pokračovat v testování. Nejsnadnějším způsobem vytváření nové komponenty je použití Experta komponent. Nicméně můžeme také provést stejné kroky ručně. Ruční vytváření komponenty probíhá v těchto krocích: vytvoření nové programové jednotky, odvození třídy komponenty, deklarování nového konstruktoru a registrace komponenty. Programová jednotka (včetně hlavičkového souboru) je samostatně překládaný modul kódu C++. Builder používá jednotky pro několik účelů. Každý formulář má svoji vlastní programovou jednotku a většina komponent (nebo logických skupin komponent) má také svou vlastní jednotku. Když vytváříme komponentu, můžeme pro komponentu vytvořit novou jednotku nebo přidat novou komponentu do existující jednotky. K vytvoření jednotky pro komponentu, zvolíme File | New Unit. Builder vytvoří nový soubor a otevře jej v Editoru kódu. Vytvořenou jednotku uložíme pod smysluplným jménem. Pro přidání komponenty k existující jednotce, zvolíme File | Open a vybereme zdrojový kód existující jednotky. Když přidáváme komponentu k existující jednotce, musíme si být jisti, že jednotka obsahuje pouze kód komponent. Přidání kódu komponent k jednotce, která obsahuje např. formulář, způsobí chybu. Jestliže máme novou nebo existující jednotku pro naši komponentu, můžeme odvodit objekt komponenty. Každá komponenta je třída odvozená od typu TComponent, od jednoho z více specializovaných potomků, jako je TControl nebo TGraphicControl, nebo od existujícího typu komponenty.
K odvození třídy komponenty, přidáme deklaraci třídy do hlavičkového souboru jednotky, která má komponentu obsahovat. Pro vytvoření např. jednoduchého typu nevizuální komponenty ji odvodíme přímo od TComponent a do hlavičkového souboru naši jednotky přidáme následující definici typu:
class TNovaKomponenta : public TComponent
Musíme také přidat příkazy vložení hlavičkových souborů, které jsou nutné pro novou komponentu. Většinou to budou direktivy:
#include <vclSysUtils.hpp>
#include <vclControls.hpp>
#include <vclClasses.hpp>
#include <vclForms.hpp>
Nová komponenta se neliší od TComponent. Tvoří rámec, ve kterém budeme budovat svoji novou komponentu. Každá nová komponenta musí mít konstruktor, který přepisuje konstruktor třídy, od které komponentu odvozujeme. Když zapisujeme konstruktor pro novou komponentu, pak tento konstruktor musí vždy volat zděděný konstruktor. V deklaraci třídy deklarujeme virtuální konstruktor a to ve veřejné části třídy. Např.
class TNovaKomponenta : public TComponent
Do CPP souboru vložíme implementaci konstruktoru:
__fastcall TNovaKomponenta::TNovaKomponenta(TComponent* Owner)
: TComponent(Owner)
Do konstruktoru přidáme kód, který chceme provést při vytváření komponenty
Nyní již můžeme registrovat TNovaKomponenta. Registrace komponenty je jednoduchý proces, který říká Builderu, které komponenty přidat do své knihovny komponent a na které stránce Palety komponent bude komponenta zobrazena. K registraci komponenty přidáme funkci nazvanou Register do souboru CPP a umístíme ji do jmenného prostoru. Jméno jmenného prostoru je jméno souboru komponenty bez přípony, ve kterém jsou s výjimkou prvního písmena všechna písmena malá. Např.
namespace Unit1
V této funkci deklarujeme otevřené pole typu TComponentClass, které obsahuje registrované komponenty:
TComponentClass classes[1] = ;
V tomto příkladě pole obsahuje právě jednu komponentu, ale můžeme přidat další komponenty, které chceme registrovat. Dále voláme funkci RegisterComponents, pro každou registrovanou komponentu. Tato funkce přebírá tři parametry: jméno stránky palety konponent, pole tříd komponent a velikost pole tříd komponent zmenšenou o 1. Jestliže přidáváme komponentu k existující registraci, můžeme přidat novou komponentu k existujícímu příkazu nebo přidat nový příkaz, který volá RegisterComponents. Jedním voláním RegisterComponents můžeme registrovat i více komponent, jestliže jsou všechny na stejné stránce palety komponent. K registraci komponenty naznané TNovaKomponenta na stránce Samples Palety komponent tedy použijeme:
namespace Unit1
void __fastcall Register()
{
TComponentClass classes[1] = ;
RegisterComponents('Samples', classes, 0);
}
Po registraci komponenty ji můžeme instalovat na Paletu komponent.
K vytvoření nové komponenty můžeme použít Experta komponent. Použití Experta komponent zjednodušuje počátek vytváření nové komponenty. Je zapotřebí zadat pouze jméno nové komponenty, typ předka a stránku Palety komponent, na které ji chceme zobrazit. Expert komponent provede stejné úlohy, jako kdybychom komponentu vytvářeli ručně. Expert komponent ale nedokáže přidat komponentu do již existující jednotky. K otevření Experta komponent zvolíme Component | New…. Po vyplnění požadovaných údajů stiskneme OK. Builder vytvoří novou jednotku obsahující deklaraci typu, funkci Register a přidá vložení potřebných hlavičkových souborů. Jednotku můžeme uložit a dát ji smysluplné jméno.
Chování komponenty můžeme testovat při běhu programu před její instalací na Paletu komponent. To je užitečné pro ladění nově vytvářených komponent, ale můžeme tuto techniku použít pro testování libovolných komponent, a to bez ohledu na to, zda komponenta je již zobrazena na Paletě komponent. Testování neinstalovaných komponent děláme emulací akcí prováděných Builderem, když uživatel umístí komponentu z palety na formulář. Provedeme tyto činnosti:
Vytvoříme novou aplikaci nebo otevřeme existující.
Vložíme hlavičkový soubor jednotky komponenty do hlavičkového souboru jednotky formuláře.
Přidáme položku objektu reprezentujícího komponentu k typu formuláře. To je hlavní rozdíl mezi způsobem přidávání komponenty a způsobem prováděným Builderem. Přidáme položku do veřejné části na závěr deklarace typu formuláře. Builder ji přidává výše, do části deklarace, která ji zpracovává. My ji tam nemůžeme přidat. Prvky v této části deklarace typu odpovídají prvkům uloženým v souboru formuláře. Přidání jména komponenty, která neexistuje na formuláři, vytvoří chybný soubor formuláře.
V konstruktoru formuláře vytvoříme komponentu. Když voláme konstruktor komponenty, musíme předat parametr specifikující vlastníka komponenty. Je vhodné jako vlastníka předávat vždy this. V metodě je this odkaz na objekt obsahující metodu.
Nastavíme vlastnost Parent. Nastavení vlastnosti Parent je vždy první věcí provedenou po vytvoření ovladače. Parent určuje komponentu, která vizuálně obsahuje ovladač (často to bývá formulář, ale může to být i panel nebo GroupBox). Normálně Parent nastavujeme na this, tj. na formulář. Parent vždy nastavujeme před nastavením ostatních vlastností ovladače. Jestliže komponenta není ovladačem (tj. jestliže některým z jejím předků není TControl), přeskočíme tento krok (nastavení vlastnosti Parent v tomto případě způsobí chybu).
Nastavíme podle potřeby další vlastnosti komponenty.
Předpokládejme, že chceme testovat novou komponentu TNovaKomponenta uloženou v jednotce pojmenované NovyCtrl. Vytvoříme nový projekt a provedeme výše popsané kroky. Dostaneme tuto jednotku formuláře (nejdříve je vypsán jeho hlavičkový soubor).
#ifndef Unit1H
#define Unit1H
#include <vclClasses.hpp>
#include <vclControls.hpp>
#include <vclStdCtrls.hpp>
#include <vclForms.hpp>
#include 'NovyCtrl.h' // 2. Přidáme NovyCtrl do hlavičkového souboru formuláře
class TForm1 : public TForm
extern TForm1 *Form1;
#endif
#include <vclvcl.h>
#pragma hdrstop
#include 'Unit1.h'
#pragma resource '*.dfm'
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
Když instalujeme komponentu na Paletu komponent, pak je knihovna komponent přebudována. Kdykoli je knihovna komponent přebudovávána (při instalaci komponenty nebo po volbě Component | Rebuild Library), Builder vytváří zdrojový soubor knihovny. Jméno zdrojového souboru knihovny je jméno knihovny s příponou CPP. Pro VCL jméno souboru je CMPLIB32.CPP. K uložení zdrojového kódu generované knihovny zvolíme Options | Environment | Library | Save Library Source Code. Před instalací naší nové komponenty přesuneme všechny soubory komponenty do adresáře BuilderLibObj. Jedná se o soubory: všechny binární soubory (DFM, RES nebo RC a DCR), všechny zdrojové soubory (CPP a PAS), všechny soubory OBJ a LIB a všechny hlavičkové soubory (H a HPP). Do knihovny komponent můžeme přidávat komponenty zapsané v C++ nebo Pascalu. K přidání komponenty do knihovny komponent zvolíme Component | Install a v zobrazeném dialogovém okně stiskneme Add k otevření dialogového okna Add Module. Zde zapíšeme jméno jednotky, kterou chceme přidat nebo zvolíme Browse a jednotku najdeme. Stiskem OK dialogové okno Add Module uzavřeme. Jména jednotek komponent, které jsme specifikovali jsou uvedeny na konci seznamu Installed Components. Jestliže v tomto seznamu vybereme jméno některé jednotky, pak jména tříd komponent umístěných v této jednotce jsou zobrazeny v seznamu Component classes. Jména tříd pro nově přidávané komponenty nejsou zobrazena. Stiskem OK uzavřeme dialogové okno Install Components a přebudujeme knihovnu. Nově instalované komponenty jsou přidány na Paletu komponent a můžeme je používat ve svých aplikacích. Paletu komponent můžeme modifikovat volbou Component | Configure Palette.
Práce s Builderem využívá myšlenku, že objekt obsahuje data i kód a že s objektem můžeme manipulovat jak během návrhu, tak i při běhu aplikace. V tomto smyslu vnímá komponenty jejich uživatel. Při vytváření nových komponent, pracujeme s objekty způsobem, který koncový uživatel nikdy nepotřebuje. Před zahájením tvorby komponent, je nutno se dobře seznámit s principy objektově orientovaného programování popsanými dále. Základní rozdíl mezi uživatelem komponent a tvůrcem komponent je ten, že uživatel manipuluje s instancemi objektů a tvůrce vytváří nové typy objektů.
Účelem definování tříd komponent je poskytnout základ pro užitečné instance. Tj. cílem je vytvořit objekt, který my nebo jiní uživatelé mohou používat v různých aplikacích, v různých situacích nebo alespoň v různých částech stejné aplikace. Jsou dva důvody k odvození nové třídy: změna implicitního typu, kterou se vyhneme opakování a přidání nových možností k typu. V obou případech, je cílem vytvoření opakovaně použitelných objektů. Ve všech programovacích úlohách se snažíme vyhnout se opakování. Jestliže potřebujeme několikrát zapsat stejné řádky kódu, pak je můžeme umístit do funkce nebo dokonce vytvořit knihovnu funkcí, kterou můžeme používat v mnoha programech. Stejný důvod je i pro komponenty. Jestliže často měníme stejné vlastnosti nebo provádíme stejné volání metod, je užitečné vytvořit nový typ komponenty, který tyto věci provede implicitně. Např. předpokládejme, že pokaždé při vytváření aplikace, chceme přidat formulář dialogového okna k provedení jisté funkce. Ačkoliv není obtížné pokaždé znova vytvořit dialogové okno, není to ale nutné. Můžeme navrhnout okno pouze jednou, nastavit jeho vlastnosti a výsledek instalovat na Paletu komponent jako znovupoužitelnou komponentu. Toto nejen redukuje opakování, ale také provádí standardizaci. Jiným důvodem pro vytváření nového typu komponenty je přidání možností, které zatím existující komponenta nemá. Lze to provést odvozením od existujícího typu komponenty (např. vytvoření specializovaného typu seznamu) nebo od abstraktního základního typu, jako je TComponent nebo TControl. Novou komponentu vždy odvozujeme od typu, který obsahuje největší podmnožinu požadovaných služeb. Objektu můžeme přidávat nové vlastnosti, ale nemůžeme je odebírat, tj. jestliže existující typ komponenty obsahuje vlastnosti, které nechceme vložit do své komponenty, musíme komponentu odvodit od předka komponenty. Např. jestliže chceme přidat nějaké možnosti k seznamu, můžeme odvodit novou komponentu od TListBox. Nicméně, jestliže chceme přidat nějaké nové možnosti, ale odstranit některé existující možnosti standardního seznamu, musíme odvodit svůj nový seznam od TCustomListBox, tj. předka TListBox. Znovuvytvoříme možnosti seznamu, které chceme použít a přidáme své nové možnosti.
Když se rozhodneme, že je nutno odvodit nový typ komponenty, musíme také určit od kterého typu komponenty svou komponentu odvodíme (viz výše). Builder poskytuje několik abstraktních typů komponent určených pro tvůrce komponent k odvozování nových typů komponent. K deklarování nového typu komponenty, přidáme deklaraci typu do hlavičkového souboru jednotky komponenty. Následující příklad je deklarace jednoduché grafické komponenty:
class TPrikladTvaru : public TGraphicControl
K dokončení deklarace komponenty vložíme do třídy deklarace vlastností, položek a metod, ale prázdná deklarace je také přípustná a poskytuje počáteční bod pro vytváření komponenty.
Pro uživatele komponent je komponenta entitou obsahující vlastnosti, metody a události. Uživatel nemusí znát co z toho komponenta zdědila a od koho to zdědila. Toto je ale značně důležité pro tvůrce komponenty. Uživatel komponenty si může být jist, že každá komponenta má vlastnosti Top a Left, které určují, kde bude komponenta zobrazena na formuláři, který ji vlastní. Nemusí znát, že všechny komponenty dědí tyto vlastnosti od společného předka TComponent. Nicméně, když vytváříme komponenty, musíme znát, který objekt dědí, od kterého objektu příslušnou část. Musíme také znát co naše komponenta dědí a můžeme tak využít zděděné služby bez jejich znovuvytvoření. Z definice třídy vidíme, že když odvozujeme třídu, odvozujeme ji od existující třídy. Třída od které odvozujeme se nazývá bezprostřední předek naší nové třídy. Předek bezprostředního předka je také předek nové třídy; jsou to všechno předkové. Nová třída je potomek svých předků. Jestliže nespecifikujeme předka třídy, Builder odvozuje třídu od implicitního předka TObject. Standardní typ TObject je předkem všech tříd v Knihovně vizuálních komponent.
Všechny vztahy předek-potomek v aplikaci tvoří hierarchii tříd. Nejdůležitější k zapamatování v hierarchii tříd je to, že každá generace tříd obsahuje více než její předkové. Tj. třída dědí vše co obsahuje předek a přidává nová data a metody nebo předefinovává existující metody. Nicméně, třída nemůže odstranit nic z toho co zdědila. Např. jestliže třída má jistou vlastnost, pak všechny přímí nebo nepřímí potomci mají také tuto vlastnost.
C++ poskytuje pět úrovně řízení přístupu k částem tříd. Řízení přístupu určuje, který kód může přistupovat ke které části třídy. Specifikací úrovní přístupu, definujeme rozhraní naší komponenty. Pokud nespecifikujeme jinak, pak položky, metody a vlastnosti přidané do naší třídy jsou soukromé. Následující tabulka ukazuje úrovně přístupu v pořadí od nejvíce omezujícího k nejméně omezujícímu:
Úroveň |
Používá se pro | ||
private |
Skrytí implementačních detailů | ||
protected |
Definování rozhraní vývojáře | ||
public |
Definování rozhraní pro běh programu | ||
__published |
Definování rozhraní pro návrh | ||
__automated |
Pro automatizaci OLE |
Deklarací části třídy jako soukromé, uděláme tuto část neviditelnou z kódu mimo třídu, pokud funkce není přítelem třídy. Soukromé části tříd jsou užitečné pro ukrytí implementačních detailů před uživateli komponent. Jelikož uživatelé objektu nemohou přistupovat k soukromé části, můžeme změnit vnitřní implementaci objektu bez vlivu na kód uživatele.
Deklarací části třídy jako chráněné, uděláme tuto část neviditelnou z kódu mimo třídu, což je stejné jako u soukromé části. Rozdíl u chráněné části je ten, že třída odvozená od tohoto typu, může přistupovat k jejím chráněným částem. Chráněné deklarace můžeme použít k definování rozhraní návrháře objektu. Tj. uživatel objektu nemá přístup k chráněným částem, ale vývojář (např. tvůrce komponent) ano. Můžeme tedy udělat rozhraní přístupným tak, že tvůrci komponent je mohou v odvozených třídách měnit, s tím, že tyto detaily nejsou viditelné pro koncové uživatele.
Deklarací části třídy jako veřejné, uděláme tuto část viditelnou pro jakýkoli kód, který má přístup ke třídě jako celku. Tj. veřejné části nemají žádné omezení. Veřejné části třídy jsou dostupné za běhu programu pro všechen kód a veřejné části třídy definují v tomto objektu rozhraní běhu programu. Rozhraní běhu programu je užitečné pro prvky, které nezpracováváme v době návrhu, jako jsou vlastnosti, které závisí na aktuálních informacích o typech za běhu programu nebo které jsou určeny pouze pro čtení. Metody, které slouží pro uživatele našich komponent také deklarujeme jako část rozhraní běhu programu. Poznamenejme, že vlastnosti určené pouze pro čtení nemůžeme používat během návrhu a uvádíme je ve veřejné části deklarace.
Deklarací částí třídy jako zveřejňované, uděláme tuto část veřejnou, která také generuje informace o typech za běhu programu pro tuto část. Mimo jiné informace o typech za běhu programu zajišťují, že Inspektor objektů může přistupovat k vlastnostem a událostem. Protože pouze zveřejňovaná část je zobrazována v Inspektoru objektů, zveřejňovaná část třídy určuje rozhraní objektu pro návrh. Rozhraní objektu pro návrh zahrnuje všechny aspekty objektu, které uživatel objektu může chtít přizpůsobit během návrhu, ale nesmí obsahovat vlastnosti, které závisí na informacích o prostředí běhu programu.
Vyřízení metod je termín použitý k popisu, jak naše aplikace určuje, který kód bude proveden při volání metody. Když zapisujeme kód, který volá metodu objektu, je to stejné, jako volání jiné funkce. Nicméně objekty mají dva různé způsoby vyřízení metod. Tyto dva typy vyřízení metod jsou: statické a virtuální. Virtuální metody se ale podstatně liší od statických metod. Typy vyřízení metod jsou důležité pro pochopení, jak vytvářet komponenty.
Všechny metody jsou statické, pokud nespecifikujeme v jejich deklaraci něco jiného. Statické metody pracují jako volání normálních funkcí. Překladač určí adresu metody a připojí metodu během překladu. Základní výhodou statických metod je, že jejich vyřízení je velmi rychlé. Protože překladač může určit adresu metody, metoda je volána přímo. Virtuální metody používají nepřímé hledání adresy jejich metod při běhu programu, což je mnohem delší. Další rozdíl u statické metody je ten, že se nemění v odvozených typech. Tj. když deklarujeme třídu, která obsahuje statickou metodu, potom odvozením nové třídy, potomek třídy sdílí přesně stejnou metodu na stejné adrese. Statické metody tedy vždy provádějí to samé, bez ohledu na aktuální typ objektu. Statickou metodu nelze předefinovat. Deklarováním metody v typu potomka se stejným jménem jako má statická metoda v objektu předka se nahradí metoda předka. Např. v následujícím kódu první komponenta deklaruje dvě statické metody. Druhá deklaruje dvě statické metody se stejným jménem, které nahradí obě metody v první komponentě.
class TPrvniKomponenta : public TComponent
class TDruhaKomponenta : public TPrvniKomponenta
Volání virtuálních metod je stejné jako volání jiných metod, ale mechanismus jejich vyřízení je složitější. Virtuální metody umožňují předefinování v objektech potomků, ale stále metodu voláme stejným způsobem. Adresa volané metody není určena při překladu, ale je hledána až při běhu aplikace. K deklaraci nové virtuální metody, přidáme direktivu virtual před deklaraci metody. Direktiva virtual v deklaraci metody vytváří položku v tabulce virtuálních metod (VTM) objektu. VTM obsahuje adresy všech virtuálních metod v typu objektu. Když odvozujeme novou třídu od existující třídy, nová třída získá svou vlastní VTM, která obsahuje všechny položky z VTM svého předka a položky dalších virtuálních metod deklarovaných v novém objektu. Potomek třídy může předefinovat některé ze zděděných virtuálních metod. Předefinování metody znamená její rozšíření nebo změnu, namísto jejího nahrazení. Třída potomka může opětovně deklarovat a implementovat libovolnou z metod deklarovaných ve svých předcích. Statickou metodu nelze předefinovat, neboť deklarace statické metody se stejným jménem jako má zděděná statická metoda, nahradí zděděnou metodu kompletně. K předefinování metody z třídy předka, předeklarujeme metodu v odvozené třídě s tím, že počet a typy parametrů se musí shodovat. Následující kód ukazuje deklaraci dvou jednoduchých komponent. První deklaruje dvě metody, každá je jiného typu vyřízení. Druhá odvozená od první, nahrazuje statickou metodu a předefinovává virtuální metodu.
class TPrvniKomponenta : public TComponent
class TDruhaKomponenta : public TPrvniKomponenta
Jednou z věcí, kterou si musíme uvědomit, když vytváříme komponentu je to, že použití existující komponenty je realizováno ukazatelem. Toto se stává důležité, když předáváme objekty jako parametry. Při předávání objektů je vhodnější použít parametr volaný hodnotou než odkazem. Objekty jsou ve skutečnosti ukazateli, které jsou již odkazem. Předáním objektu odkazem je vlastně předáván odkaz na odkaz.
Vlastnosti jsou nejdůležitější částí komponent, neboť uživatelé komponent je mohou vidět a pracovat s nimi během návrhu a získat tak bezprostřední zpětnou vazbu na reakci komponenty v reálném čase. Vlastnosti jsou také důležité, neboť usnadňují používání komponent. Vlastnosti poskytují významné výhody a to jak pro tvůrce komponent, tak i pro jejich uživatele. Nejzřejmější výhodou je, že vlastnost může být v době návrhu zobrazena v Inspektoru objektů. To zjednodušuje naše programování, neboť namísto zadávání několika parametrů při vytváření objektu, zpracujeme hodnoty přiřazené uživatelem. Pro uživatele komponent se vlastnosti podobají proměnným. Uživatel může nastavovat nebo číst hodnoty vlastností, jako kdyby tyto vlastnosti byly položkami objektů. Rozdíl je pouze v tom, že vlastnost nemůže být použita jako parametr volaný odkazem. Vlastnosti poskytují více možností než položky objektu neboť: Uživatel může nastavovat vlastnosti během návrhu. To je velmi důležité, neboť narozdíl od metod, které jsou dostupné pouze při běhu aplikace, vlastnosti slouží k přizpůsobení komponenty před spuštěním aplikace. Komponenta nemusí obsahovat mnoho metod, které by zapouzdřovaly tyto vlastnosti. Narozdíl od položek objektu, vlastnosti mohou skrýt implementační detaily před uživateli. Např. data mohou být uložena v zakódovaném tvaru, ale když nastavujeme nebo čteme hodnotu vlastnosti potřebujeme je nezakódované. Přestože hodnota vlastnosti může být číslo, komponenta může hledat hodnotu v databázi nebo k získání hodnoty provádět složité výpočty. Vlastnosti poskytují k příkazu přiřazení vedlejší efekt. Když provedeme přiřazení, je volána metoda, která může dělat cokoliv. Příkladem je vlastnost Top všech komponent. Přiřazení nové hodnoty vlastnosti Top, neznamená jenom změnu nějaké uložené hodnoty, ale způsobí i přemístění a překreslení komponenty samotné. Efekt nastavení vlastnosti není omezen na nějakou komponentu. Nastavení vlastnosti Down komponenty urychlovacího tlačítka na true, způsobí nastavení Down všech ostatních urychlovacích tlačítek ve skupině na false. Implementace metody pro vlastnost může být virtuální, což znamená, že může provádět různé věci v různých komponentách.
Vlastnost může být libovolného typu. Důležitým aspektem volby typu pro naši vlastnost je to, že různé typy jsou různě zobrazovány v Inspektoru objektů. Inspektor objektů používá typ vlastnosti k určení co uživatel chce zobrazit. Při registraci komponenty můžeme specifikovat různé editory vlastností. V následující tabulce je uvedeno jak vlastnost je zobrazena v Inspektoru objektů.
Typ vlastnosti |
Zacházení s vlastností v Inspektoru objektů |
Jednoduchý |
Číselné, znakové a řetězcové vlastnosti se zobrazují v Inspektoru objektů jako čísla, znaky nebo řetězce. Uživatel může zadávat a editovat hodnotu vlastnosti přímo. |
Výčtový |
Vlastnosti výčtových typů (včetně bool) zobrazují hodnotu tak, jak je definována ve zdrojovém kódu. Uživatel může cyklicky procházet možnými hodnotami dvojitým kliknutím ve sloupci hodnot. Hodnotu můžeme také vybírat ze seznamu obsahujícího všechny možné hodnoty výčtového typu. |
Množina |
Vlastnosti typu množina se zobrazují v Inspektoru objektů jako množiny. Rozšířením množiny, uživatel může zacházet s každým prvkem množiny jako s logickou hodnotou: true, jestliže prvek je obsažen v množině nebo false není-li. |
Objekt |
Vlastnost, která je sama objektem, často má svůj vlastní editor vlastností. Nicméně, jestliže objekt, který je vlastností má také zveřejňované vlastnosti, pak Inspektor objektů umožňuje uživateli rozšířit seznam vlastností objektu a editovat je samostatně. Objektové vlastnosti musí být odvozeny od TPersistent. |
Pole |
Pole vlastností musí mít svůj vlastní editor vlastností. Inspektor objektů nemá zabudovánu podporu pro editaci pole vlastností. |
Všechny komponenty dědí vlastnosti od svých předků. Když odvozujeme novou komponentu od existujícího typu komponenty, naše nová komponenta dědí všechny vlastnosti ze třídy předka. Jestliže odvozujeme komponentu od jednoho z abstraktních typů, pak zděděné vlastnosti jsou chráněné nebo veřejné, ale ne zveřejňované. Aby chráněná nebo veřejná vlastnost byla přístupná pro uživatele komponenty, musíme ji opětovně deklarovat jako zveřejňovanou. To provedeme přidáním deklarace zděděné vlastnosti do deklarace třídy potomka. Jestliže odvozujeme komponentu od TWinControl, komponenta např. zdědí vlastnost Ctl3D, ale tato vlastnost je chráněná a uživatel komponenty nemůže k Ctl3D během návrhu ani při běhu programu. Opětovnou deklarací Ctl3D v naši nové komponentě, můžeme změnit úroveň ochrany na veřejnou nebo zveřejňovanou. Následující kód ukazuje opětnou deklaraci Ctl3D jako zveřejňovanou, což ji zpřístupní během návrhu:
class TPrikladKomponenty : public TWinControl
Opětovnou deklarací můžeme pouze zmírnit omezení přístupu a nelze je zvětšit. Chráněnou vlastnost lze změnit na veřejnou, ale nelze změnit veřejnou vlastnost na chráněnou. Při opakované deklaraci, specifikujeme pouze jméno vlastnosti, typ a další informace se neuvádí. Můžeme také deklarovat novou implicitní hodnotu a ukládací specifikátory.
Deklarace vlastnosti a její implementace je snadná. Přidáme deklaraci vlastnosti k deklaraci třídy naší komponenty. V deklaraci vlastnosti specifikujeme tři věci: jméno vlastnosti, typ vlastnosti a metody pro čtení a nastavování hodnot vlastnosti. Vlastnosti komponent minimálně musíme deklarovat ve veřejné části deklarace typu objektu komponenty, což umožní číst a nastavovat vlastnosti z vnějšku komponenty při běhu programu. K vytvoření editovatelných komponent během návrhu musíme deklarovat vlastnost ve zveřejňované části deklarace třídy komponenty. Zveřejňované vlastnosti jsou automaticky zobrazovány v Inspektoru objektů. Veřejné vlastnosti jsou přístupné pouze za běhu programu. Následuje typická deklarace vlastnosti:
class TNaseKomponenta : public TComponent
//deklarace vlastnosti
Není žádné omezení jak ukládat data vlastnosti. Komponenty Builderu používají ale tyto konvence: Data vlastnosti jsou ukládány v položkách třídy. Identifikátor pro položku vlastnosti třídy začíná písmenem F a následuje jméno vlastnosti. Např. data pro vlastnost Width definovanou v TControl jsou uložena v položce objektu nazvané FWidth. Položku objektu pro vlastnost deklarujeme jako soukromou. To zajistí, že komponenta, která deklaruje vlastnost, má k ní přístup, ale uživatelé komponenty a potomci komponenty ne. Potomci komponenty mohou používat samotnou zděděnou vlastnost, ale nemají přístup k vnitřnímu uložení dat komponenty. Základním principem těchto konvencí je, že pouze implementace přístupových metod vlastnosti mohou přistupovat k datům této vlastnosti. Jestliže metoda nebo jiná vlastnost potřebuje změnit tato data, musí to provést prostřednictvím vlastnosti a ne přímým přístupem k uloženým datům. To zajišťuje, že implementaci zděděné metody můžeme měnit bez vlivu na potomky komponenty.
Nejjednodušším způsobem zpřístupnění dat je přímý přístup. Tj. části read a write deklarace vlastnosti specifikují, že přiřazení nebo čtení hodnoty vlastnosti probíhá přímo s položkou vnitřního uložení bez volání přístupové metody. Přímý přístup je užitečný, když vlastnost nemá vedlejší efekty, ale chceme ji zpřístupnit v Inspektoru objektů. Často používáme přímý přístup pro část read deklarace vlastnosti a přístupovou metodu pro část write neboť obvykle aktualizujeme stav komponenty na základě nové hodnoty vlastnosti. Následující deklarace typu komponenty ukazuje vlastnost, která používá přímý přístup pro čtení i zápis:
class TPrikladKomponenty : public TComponent
Syntaxe deklarace vlastnosti umožňuje, aby části read a write deklarace vlastnosti specifikovaly přístupové metody namísto položek objektu. Bez ohledu na implementaci částí read a write jisté vlastnosti, tato implementace musí být soukromá a potomci komponent mohou k zděděné vlastnosti přistupovat. To zajistí, že použití vlastnosti není ovlivněno změnami v implementaci. Udělání přístupových metod soukromými také zajistí, že uživatelé komponent nemohou tyto metody volat k modifikaci vlastnosti. Čtecí metoda pro vlastnost je funkce, která nemá parametry a vrací hodnotu stejného typu jako má vlastnost. Podle konvencí, jméno funkce začíná Get a pokračuje jménem vlastnosti. Např. čtecí metoda pro vlastnost nazvanou Pocet by se měla jmenovat GetPocet. Výjimkou k “funkce je bez parametrů” je případ pole parametrů, které předává jejich indexy jako parametry. Čtecí metoda pracuje s vnitřním uložením dat a vytváří hodnotu vlastnosti příslušného typu. Je-li vlastnost typu “pouze pro zápis”, není nutné deklarovat čtecí metodu. Vlastnosti, určené pouze pro zápis se používají velmi zřídka a obecně nejsou moc užitečné. Zápisová metoda pro vlastnost je vždy funkce typu void s jedním parametrem, který je stejného typu jako vlastnost. Parametr může být předáván odkazem nebo hodnotou a jeho jméno může být libovolné. Podle konvencí jméno funkce je Set následované jménem vlastnosti. Např. zápisová metoda pro vlastnost nazvanou Pocet by se měla jmenovat SetPocet. Hodnota předaná v parametru je použita k nastavení nové hodnoty vlastnosti a zápisová metoda musí provést operace potřebné k vytvoření příslušné hodnoty ve vnitřním formátu. Je-li vlastnost typu pouze pro čtení, není nutné deklarovat zápisovou metodu. Je vhodné testovat, zda se nová hodnota liší od současné hodnoty před jejím přiřazením. Např. následuje příklad zápisové metody pro vlastnost typu int nazvanou Pocet, která ukládá svou aktuální hodnotu v položce FPocet:
void __fastcall TMojeKomponenta::SetPocet(int Hodnota)
Když deklarujeme vlastnost, můžeme pro ní volitelně deklarovat implicitní hodnotu. Implicitní hodnota vlastnosti komponenty je hodnota nastavená pro tuto vlastnost konstruktorem komponenty. Např. když umístíme komponentu z Palety komponent na formulář, Builder vytváří komponentu voláním konstruktoru komponenty, který určuje počáteční hodnoty vlastností komponenty. Builder používá deklarované implicitní hodnoty k určení, zda ukládat vlastnost v souboru formuláře. Jestliže implicitní hodnotu pro vlastnost nespecifikujeme, pak Builder vlastnost vždy ukládá. K deklarování implicitní hodnoty pro vlastnost připojíme direktivu default k deklaraci (nebo opětovné deklaraci) následovanou implicitní hodnotou. Deklarací implicitní hodnoty v deklaraci vlastnosti nenastavujeme aktuálně vlastnost na tuto hodnotu. Jako tvůrce komponenty musíme zajistit, že konstruktor komponenty nastaví vlastnost na tuto hodnotu. Když opakovaně deklarujeme vlastnost, můžeme specifikovat, že vlastnost nemá implicitní hodnotu, i když zděděná vlastnost ji má. K určení, že vlastnost nemá implicitní hodnotu, připojíme direktivu nodefault k deklaraci vlastnosti. Když deklarujeme vlastnost poprvé, není nutno specifikovat nodefault, protože absence deklarace implicitní hodnoty znamená totéž. Následuje deklarace komponenty, která obsahuje vlastnost IsTrue typu bool s implicitní hodnotou true a konstruktor nastavující implicitní hodnotu:
class TPrikladKomponenty : public TComponent
__fastcall TPrikladKomponenty::TPrikladKomponenty(TComponent * Owner)
: TComponent(Owner)
Pokud by implicitní hodnota pro IsTrue měla být false, potom ji není nutno explicitně nastavovat v konstruktoru, neboť všechny třídy (a tedy i komponenty) vždy inicializují všechny své položky na nulu a “nulová” logická hodnota je false.
Přejděme ke konkrétnímu příkladu. Budeme modifikovat standardní komponentu Memo k vytvoření komponenty, která implicitně neprovádí lámání slov a má žluté pozadí. Je to velmi jednoduchý příklad, ale ukazuje vše, co je zapotřebí provést k modifikaci existující komponenty. Implicitně hodnota vlastnosti WordWrap komponenty Memo je true. Jestliže používáme několik těchto komponent u nichž nechceme provádět lámání slov (automatický přechod na další řádek při dosažení konce řádku) můžeme snadno vytvořit novou komponentu, která implicitně lámání slov neprovádí (implicitní hodnotu vlastnosti WordWrap nastavíme na false). Modifikace existující komponenty probíhá ve dvou krocích: vytvoření a registrace komponenty a modifikace objektu komponenty. Základní proces je ale vždy stejný, ale u složitějších komponent budeme muset provést více kroků pro přizpůsobení nového objektu. Vytváření každé komponenty začíná stejně: vytvoříme programovou jednotku, odvodíme třídu komponenty, registrujeme ji a instalujeme ji na Paletu komponent. V našem příkladě použijeme uvedený obecný postup s těmito specifikami: jednotku komponenty nazveme Memos, od TMemo odvodíme nový typ komponenty nazvaný TWrapMemo a registrujeme TWrapMemo na stránce Samples Palety komponent. Výsledek naši práce je tento (nejdříve je uveden výpis hlavičkového souboru):
#ifndef MemosH
#define MemosH
#include <vclSysUtils.hpp>
#include <vclControls.hpp>
#include <vclClasses.hpp>
#include <vclForms.hpp>
#include <vclStdCtrls.hpp>
class TWrapMemo : public TMemo
#endif
#include <vclvcl.h>
#pragma hdrstop
#include 'Memos.h'
namespace Memos
RegisterComponents('Samples', classes, 0);
}
V tomto případě jsme nepoužili k vytvoření komponenty Experta komponent, ale vytvořili jsme ji manuálně. Pokud bychom použili Experta komponent, pak by byl do vytvořené třídy automaticky přidán konstruktor.
Všechny komponenty nastavují své hodnoty vlastností při vytváření. Když umístíme komponentu během návrhu na formulář nebo když spustíme aplikaci vytvářející komponentu a přečteme její vlastnosti ze souboru formuláře, je nejprve volán konstruktor komponenty k nastavení implicitních hodnot komponenty. V případě zavedení komponenty ze souboru formuláře, po vytvoření objektu s jeho implicitními hodnotami vlastností, aplikace dále nastavuje vlastnosti změněné při návrhu a když je komponenta zobrazena vidíme ji tak, jak jsme ji navrhli. Konstruktor ale vždy určuje implicitní hodnoty vlastností. Pro změnu implicitní hodnoty vlastnosti, předefinujeme konstruktor komponenty k nastavení určené hodnoty. Když předefinujeme konstruktor, pak nový konstruktor musí vždy volat zděděný konstruktor a to dříve než provedeme cokoliv jiného. V našem příkladě, naše nová komponenta musí předefinovat konstruktor zděděný od TMemo k nastavení vlastnosti WordWrap na false a vlastnosti Color na clYellow. Přidáme tedy deklaraci předefinovaného konstruktoru do deklarace třídy a zapíšeme nový konstruktor do CPP souboru:
class TWrapMemo : public TMemo
__fastcall TWrapMemo::TWrapMemo(TComponent* Owner)
: TMemo(Owner)
Nyní můžeme instalovat novou komponentu na Paletu komponent a přidat ji na formulář. Vlastnost WordWrap je nyní implicitně false a vlastnost Color clYellow. Jestliže změníme (nebo vytvoříme) novou implicitní hodnotu vlastnosti, musíme také určit, že hodnota je implicitní. Jestliže to neprovedeme, Builder nemůže ukládat a obnovovat hodnotu vlastnosti. Když Builder ukládá popis formuláře do souboru formuláře, ukládá pouze hodnoty vlastností, které se liší od jejich implicitních hodnot. Má to dvě výhody: zmenšuje to soubor formuláře a urychluje zavádění formuláře. Jestliže vytvoříme vlastnost nebo změníme implicitní hodnotu vlastnosti je vhodné aktualizovat deklaraci vlastnosti na novou implicitní hodnotu. Ke změně implicitní hodnoty vlastnosti, opětovně deklarujeme vlastnost a připojíme direktivu default s novou implicitní hodnotou. Není nutno opětovně deklarovat prvky vlastnosti, pouze jméno a implicitní hodnotu. V našem příkladě provedeme:
class TWrapMemo : public TMemo
__property WordWrap = ;
Specifikace implicitní hodnoty vlastnosti nemá vliv na celkovou práci komponenty. Musíme stále explicitně nastavit implicitní hodnotu v konstruktoru komponenty. Rozdíl je ve vnitřní práci aplikace: Builder nezapisuje WordWrap do souboru formuláře, jestliže je false, neboť předpokládá, že konstruktor nastaví tuto hodnotu automaticky. Totéž platí i o vlastnosti Color.
Vytvořte komponentu FontCombo (kombinované okno volby písma). Tuto komponentu odvoďte od komponenty ComboBox tak, že změníte implicitní hodnotu vlastnosti Style na csDropDownList a do vlastnosti Items přiřadíte Screen->Fonts. Vyzkoušejte použití této komponenty v nějaké aplikaci (bude signalizována chyba).
Namísto přiřazení seznamu písem v konstruktoru komponenty FontCombo je nutno jej přiřadit v metodě CreateWnd (inicializuje parametry), kterou předefinujeme (nejprve voláme metodu předka a potom provedeme přiřazení). Vyzkoušejte provést tuto změnu. Nyní již tuto komponentu můžeme používat bez problémů.
V dalším zadání se pokusíme modifikovat komponentu ListBox a to tak, aby v zobrazeném textu mohly být použity znaky tabulátorů (ke stylu okna je nutno přidat příznak LBS_USETABSTOPS). Komponentu nazveme TabList a předefinujeme v ní metodu CreateParams, kterou Builder používá ke změně některých standardních hodnot používaných při tvorbě komponenty. Tato metoda bude vypadat takto:
void __fastcall TTabBox::CreateParams(Controls::TCreateParams &Params)
Komponentu vyzkoušejte.
Některé vlastnosti mohou být indexované, podobně jako pole. Mají více hodnot, které rozlišujeme indexem. Příkladem ve standardních komponentách je vlastnost Lines komponenty Memo. Lines je indexovaný seznam řetězců, které tvoří text komponenty a můžeme k nim přistupovat jako k poli řetězců. V tomto případě, pole vlastností dává uživateli přirozený přístup k jistému prvku (řetězci - řádku) ve větší množině dat (textu komponenty). Pole vlastností pracuje stejně jako ostatní vlastnosti a deklarujeme je většinou stejně (jsou zde pouze tyto rozdíly): Deklarace vlastnosti obsahuje jeden nebo více indexů určitého typu. Indexy mohou být libovolného typu. Části read a write deklarace vlastností, jsou-li specifikovány, pak musí být metodami. Nelze zde specifikovat položky třídy. Přístupové metody pro čtení a zápis hodnoty vlastnosti přibírají další parametry, které odpovídají indexu nebo indexům. Parametry musí být ve stejném pořadí a stejného typu jako indexy specifikované v deklaraci vlastnosti. Na rozdíl od indexu pole, typ indexu pro pole vlastností nemusí být celočíselného typu. Např. jako index pole vlastností může být i řetězec. Můžeme se také odkazovat na individuální prvky pole vlastností, neležící v rozsahu vlastnosti. Následuje deklarace vlastnosti, která vrací na základě celočíselného indexu řetězec:
Class TPrikladKomponenty : public TComponent
System::AnsiString __fastcall TPrikladKomponenty::GetJmenoCisla(int Index)
return Vysledek;
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 vlastností 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 vlastností vyžaduje pět kroků: odvození třídy editoru vlastností, editace vlastnosti jako text, editace vlastnosti jako celek, specifikaci atributů editoru a registrace editoru vlastností.
Soubor DSGNINTF.HPP definuje několik typů editoru vlastností, všechny jsou odvozeny od TPropetryEditor. Když vytváříme editor vlastností, můžeme třídu editoru vlastností odvodit přímo od TPropertyEditor nebo nepřímo od jednoho z typů editoru vlastností popsaných v další tabulce (odvozujeme nový objekt od jednoho z existujícího typu editoru vlastností). 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 vlastností 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šechny 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. |
Jedním z jednoduchých editorů vlastností je TFloatProperty, editor pro vlastnosti, které jsou reálnými čísly. Následuje jeho deklarace:
class TFloatProperty : public TPropertyEditor
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. Objekt editoru vlastností poskytuje virtuální metody, které můžeme předefinovat pro převod mezi textovou reprezentací a aktuální hodnotou. Tyto metody jsou nazvané GetValue a SetValue. Náš editor vlastností 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 |
ukazatel metody (události) |
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 vlastností 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 vlastností. Jestliže vlastnost nemá řetězcovou hodnotu, naše GetValue musí převést hodnotu na její řetězcovou reprezentaci. Metoda SetValue editoru vlastností 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.
Volitelně můžeme poskytnout dialogové okno, ve kterém může uživatel viditelně editovat vlastnost. Toto je užitečné pro editory vlastností jejichž vlastnosti jsou sami 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 vlastností, předefinujeme metodu Edit třídy editoru vlastností. 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.
Editor vlastností 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 objektu 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í hodnotě). |
Vlastnost Color má několik možností, jak ji uživatel zvolí 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()
Vytvořený editor vlastností se musí v Builderu registrovat. Registrací editoru vlastností 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 vlastností voláme metodu RegisterPropetryEditor. Tato metoda 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 standardní komponenty na Paletě komponent:
namespace Newcomp
Tři příkazy v této funkci ukazují různé použití RegisterPropertyEditor: První příkaz je nejtypičtější. Registruje editor vlastností TComponentProperty pro všechny vlastnosti typu TComponent (nebo potomků TComponent, které nemají registrován vlastní editor). Obecně, když registrujeme editor vlastností, 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 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.
Události jsou velmi důležitou částí komponent. Jsou propojením mezi výskytem v systému (jako je např. akce uživatele nebo změna zaostření), na který komponenta může reagovat a částí kódu, který reaguje na tento výskyt. Reagující kód je obsluha události a je většinou zapisována uživatelem komponenty. Pomocí událostí, vývojář aplikace může přizpůsobit chování komponenty a to bez nutnosti změny samotného objektu. Jako tvůrce komponenty, použijeme události k povolení vývojáři aplikace přizpůsobit chování komponenty. Události pro mnoho akcí uživatele (např. akce myši) jsou zabudovány ve všech standardních komponentách Builderu, ale můžeme také definovat nové události. Builder implementuje události jako vlastnosti.
Obecně řečeno, událost je mechanismus, který propojuje výskyt s určitým kódem. Více specificky, událost je závěr (ukazatel), který ukazuje na určitou metodu v určité instanci třídy. Z perspektivy uživatele komponenty, událost je jméno svázané s událostí systému, jako je např. OnClick, kterému uživatel může přiřadit volání určité metody. Např. stisknutí tlačítka nazvaného Button1 má metodu OnClick. Implicitně Builder generuje obsluhu události nazvanou Button1Click ve formuláři, který obsahuje tlačítko a přiřadí ji OnClick. Když se vyskytne událost kliknutí na tlačítku, tlačítko volá metodu přiřazenou OnClick, v tomto případě Button1Click.
Uživatel stiskne Button1 Button1->OnClick ukazuje na Je provedeno
Form->Button1Click Form1->Button1Click
výskyt událost obsluha události
Uživatel komponenty tedy používá událost ke specifikaci uživatelského kódu, který aplikace volá při výskytu jisté události.
Builder používá k implementaci událostí závěry. Závěr je speciální typ ukazatele, který ukazuje na určitou metodu v určité instanci objektu. Jako tvůrce komponenty můžeme používat závěr jako adresu místa. Náš kód detekuje výskyt události a je volána metoda (je-li) specifikovaná uživatelem pro tuto událost. Závěr obhospodařuje skrytý ukazatel na instanci třídy. Když uživatel přiřadí obsluhu k události komponenty, nepřiřadí metodu jistého jména, ale jistou metodu jisté instance objektu. Tato instance je obvykle formulář obsahující komponentu, ale nemusí jim být. Všechny ovladače např. dědí virtuální metodu nazvanou Click pro zpracování události kliknutí:
virtual void __fastcall Click(void);
Jestliže uživatel má přiřazenou obsluhu k události OnClick ovladače, pak výskytem kliknutí na ovladači je volání přiřazené obsluhy. Jestliže obsluha není přiřazena, neprovádí se nic.
Komponenty používají k implementaci svých událostí vlastnosti. Narozdíl od jiných vlastností, události nemohou použít metody k implementování částí read a write. Je zde nutno použít soukromou položku objektu a to stejného typu jako je vlastnost. Podle konvencí, jméno položky je stejné jako jméno vlastnosti, ale na začátek je přidáno písmeno F. Např. ukazatel metody OnClick je uložen v položce nazvané FOnClick typu TNotifyEvent a deklarace vlastnosti události OnClick je tato:
class TControl : public TComponent
protected:
__property TNotifyEvent OnClick =
Stejně jako u jiných vlastností, můžeme nastavovat nebo měnit hodnotu události při běhu aplikace a pomocí Inspektora objektů může uživatel komponent přiřazovat obsluhu při návrhu.
Protože událost je ukazatel na obsluhu události, typ vlastnosti události musí být závěrem. Podobně, libovolný kód použitý jako obsluha události musí být odpovídajícím typem metody třídy. Všechny metody obsluh událostí jsou funkce typu void. Jsou kompatibilní s událostí daného typu, metoda obsluhy událostí musí mít stejný počet a typy parametrů a musí být předány ve stejném pořadí. Builder definuje typy metod pro všechny své standardní události. Když vytváříme svou vlastní událost, můžeme použít existující typ (pokud vyhovuje) nebo definovat svůj vlastní. Přestože obsluha události je funkce, nesmíme hodnotu funkce nikdy použít při zpracování události (funkce musí být typu void). Prázdná funkce vrací nedefinovaný výsledek, prázdná obsluha události, která by vracela hodnotu, by byla chybná. Jelikož obsluha událostí nemůže vracet hodnotu, musíme získávat informace zpět z uživatelova kódu prostřednictvím parametrů volaných odkazem. Příkladem předávání parametrů volaných odkazem obsluze události je událost stisku klávesy, která je typu TKeyPressEvent. TKeyPressEvent definuje dva parametry, první, indikující, který objekt generuje událost a druhý indikující, která klávesa byla stisknuta:
typedef void __fastcall (__closure *TKeyPressEvent)(TObject *Sender,Char &Key);
Normálně, parametr Key obsahuje znak stisknutý uživatelem. V některých situacích může uživatel komponenty chtít tento znak změnit. Např. může chtít převést znaky malých písmen na odpovídající písmena velká. V tomto případě může uživatel definovat následující obsluhu události pro klávesnici:
void __fastcall TForm1::Edit1KeyPress(TObject *Sender,Char &Key)
Při vytváření událostí komponenty musíme pamatovat na to, že uživatel našich komponent nemusí připojit obsluhu k události. To znamená, že naše komponenta negeneruje chybu, pokud uživatel komponenty nepřipojí obsluhu k jisté události.
Všechny ovladače v Builderu dědí události pro nejdůležitější události Windows. Tyto události nazýváme standardní události. Všechny tyto události zabudované v abstraktních ovladačích, jsou implicitně chráněné, což znamená, že koncový uživatel k nim nemůže připojit obsluhu. Když vytvoříme ovladač, můžeme zvolit, zda bude událost viditelná pro uživatele našeho ovladače. Jsou dvě kategorie standardních událostí: události definované pro všechny ovladače a události definované pouze pro standardní okenní ovladače. Nejzákladnější události jsou definovány v typu objektu TControl. Všechny ovladače (okenní, grafické nebo uživatelské) dědí tyto události. Následuje seznam událostí přístupných ve všech ovladačích:
OnClick OnDragDrop OnEndDrag OnMouseMove OnDblClick OnDragOver
OnMouseDown OnMouseUp
Všechny standardní události mají chráněné virtuální metody deklarované v TControl, jejichž jméno odpovídá jménu události, ale je bez “On” na začátku. Např. událost OnClick volá metodu jména Click. Mimo události společné pro všechny ovladače, mají ovladače odvozené od TWinControl další události (mají také příslušné metody):
OnEnter OnKeyDown OnKeyPress OnKeyUp OnExit
Deklarace standardních události jsou chráněné a chráněné jsou i metody, které jim odpovídají. Jestliže chceme tyto vlastnosti zpřístupnit uživateli při běhu programu nebo při návrhu, musíme opětovně deklarovat vlastnost události jako veřejnou nebo zveřejňovanou. Opětovná deklarace vlastnosti bez specifikace její implementace zachovává implementovanou metodu, ale změní úroveň ochrany. Tím můžeme zviditelnit standardní události. Jestliže vytváříme komponentu, např. chceme zpřístupnit událost OnClick během návrhu, přidáme do deklarace typu komponenty:
class TMujOvladac : public TCustomControl
Jestliže chceme změnit způsob reakce komponenty na jistou třídu událostí, zapíšeme příslušný kód a přiřadíme jej události. Pro uživatele komponenty to je přesně to co chce. Nicméně, když vytváříme komponentu, není to co chceme, protože musíme udržet událost přístupnou pro uživatele komponenty. Je to smysl chráněné implementace metod přiřazených ke každé standardní události. Předefinováním implementace metody, můžeme modifikovat vnitřní obsluhu události a voláním zděděné metody můžeme obsloužit standardní zpracování, včetně uživatelova kódu. Je důležité kdy voláme zděděnou metodu. Obecné pravidlo je volat zděděnou metodu nejdříve, a použít kód původní obsluhy události dříve než svůj přizpůsobený. Nicméně, někdy chceme provést svůj kód před voláním zděděné metody. Např. jestliže zděděný kód je nějak závislý na stavu komponenty a náš kód mění tento stav, pak chceme změnit stav a nechat uživatelův kód reagovat na změněný stav. Předpokládejme např. že zapisujeme komponentu a chceme modifikovat způsob reakce nové komponenty na kliknutí. Namísto přiřazení obsluhy k události OnClick, jak by to provedl uživatel komponenty, předefinujeme chráněnou metodu Click:
void __fastcall TMujOvladac::Click()
Definování nových událostí je relativně vzácné. Mnohem častěji provádíme předefinování již existujících událostí. Nicméně někdy, když chování komponenty je značně odlišné, pak je vhodné definovat pro ni událost. Definování události probíhá ve čtyřech krocích: spuštění události, definování typu obsluhy, deklarace události a volání události. První co provedeme, když definujeme svou vlastní událost, která neodpovídá žádné standardní události, je určení co událost spustí. Pro některé události je odpověď zřejmá. Např. když uživatel stiskne levé tlačítko myši, Windows zasílá zprávu WM_LBUTTONDOWN aplikaci. Po příjmu této zprávy komponenta volá svou metodu MouseDown, která dále volá nějaký kód, který uživatel má připojen k události OnMouseDown. Ale u některých událostí je obtížnější určit, co specifikuje externí událost. Např. posuvník má událost OnChange, spouštěnou několika typy výskytů, včetně klávesnice, kliknutí myši nebo změnou v jiném ovladači. Když definujeme svou událost, musíme zajistit, že všechny možné výskyty spustí naši událost.
Jsou dva typy výskytů, pro které musíme události ošetřit: změna stavu a akce uživatele. Mechanismus zpracování je stejný, ale liší se sémanticky. Událost akce uživatele je téměř vždy spouštěna zprávou Windows, indikující, že uživatel provedl něco na co naše komponenta má reagovat. Událost změny stavu je také svázána se zprávou od Windows (např. změna zaostření nebo povolení něčeho), ale může také vzniknou na základě změny vlastnosti nebo jiného kódu. Musíme definovat, že to vše může spustit událost. Když určíme jak naše událost vznikne, musíme definovat jak ji chceme obsloužit. To znamená určit typ obsluhy události. V mnoha případech, obsluhy pro události definujeme sami jednoduchým oznámením nebo typem specifickým události. To také umožňuje získat informace zpět z obsluhy. Oznamovací událost je událost, která pouze říká, se jistá událost nastala a nespecifikuje žádné informace o ní. Oznámení používá typ TNotifyEvent, který má pouze jeden parametr a je to odesilatel události. Tedy všechny obsluhy pro oznámení znají o události pouze to, o jakou třídu události se jedná a která komponenta událost způsobila. Např. události kliknutí jsou oznámení. Když zapisujeme obsluhu pro událost kliknutí, vše co známe je to, že kliknutí nastalo a na které komponentě bylo kliknuto. Oznámení jsou jednosměrný proces. Není mechanismus k poskytnutí zpětné vazby nebo zabránění budoucí obsluhy oznámení. V některých případech nám ale tyto informace nestačí. Např. jestliže událost je událost stisku klávesy, je pravděpodobné, že chceme také znát, která klávesa byla stisknuta. V těchto případech požadujeme typ obsluhy, který obsahuje parametry s nějakými nezbytnými informacemi o události. Jestliže naše událost je generována v reakci na zprávu, je pravděpodobné, že parametry předávané obsluze události získáme přímo z parametrů zprávy. Protože všechny obsluhy událostí jsou funkce vracející void, jediný způsob k předání informací zpět z obsluhy je pomocí parametru volaného odkazem. Naše komponenta může použít tyto informace k určení jak a co událost provede po provedení obsluhy uživatele. Např. všechny události klávesnice (OnKeyDown, OnKeyUp a OnKeyPress) předávají hodnotu stisknuté klávesy v parametru volaném odkazem jména Key. Obsluha události může změnit Key a tak aplikace vidí jinou klávesu než která způsobila událost. To je např. způsob jak změnit zapsaný znak na velká písmena.
Když jsme určili typ naší obsluhy události, můžeme deklarovat ukazatel metody a vlastnosti pro událost. Události dáme smysluplné a popisné jméno tak, aby uživatel pochopil, co událost dělá. Je vhodné, aby bylo konzistentní s jmény podobných vlastností v jiných komponentách. Jména všech standardních událostí v Builderu začínají “On”. Je to pouze konvence, překladač nevyžaduje její dodržování. Inspektor objektů určuje že vlastnost je událost podle typu vlastnosti: všechny vlastnosti ukazatelů metod jsou považovány za události a jsou zobrazeny na stránce událostí.
Obecně je vhodné centralizovat volání události, tj. vytvořit virtuální metodu v naši komponentě, která volá uživatelskou obsluhu události (pokud ji uživatel přiřadí) a provádí nějaké implicitní zpracování. Umístění všech volání událostí na jednom místě zajistí, že nějaká odvozená nová komponenta od naší komponenty může přizpůsobit obsloužení události předefinováním jen jedné metody, namísto hledáním v našem kódu místa, kde událost je volána. Nesmíme nikdy vytvořit situaci ve které prázdná obsluha události způsobí chybu, tj. vlastní funkčnost našich komponent nesmí záviset na jisté reakci z kódu uživatelovi obsluhy události. Z tohoto důvodu, prázdná obsluha musí produkovat stejný výsledek jako neobsloužená. Komponenty nikdy nesmějí požadovat aby uživatel je použil jistým způsobem. Důležitým aspektem je princip, že uživatel komponent nemá žádné omezení na to, co může s nimi dělat v obsluze události. Jelikož prázdná obsluha se má chovat stejně jako žádná obsluha, kód pro volání uživatelské obsluhy má vypadat takto:
if (OnClick) OnClick(this);
// provedení implicitního zpracování
Nikdy nesmíme použít tento způsob:
if (OnClick) OnClick(this);
else
// provedení implicitního zpracování
Pro některé typy událostí, uživatel může chtít nahradit implicitní zpracování. K umožnění aby to mohl udělat, musíme přidat parametr volaný odkazem k obsluze a testovat jej na jistou hodnotu při návratu z obsluhy. Když např. zpracováváme událost stisku klávesy, uživatel může požadovat implicitní zpracováni nastavením parametru Key na nulový znak (viz následující příklad):
if (OnKeyPress) OnKeyPress(this, &Key);
if (Key != NULL) // provedení implicitního zpracování
Skutečný kód se nepatrně liší od zde uvedeného (spolupracuje se zprávou Windows), ale logika je stejná. Implicitně komponenta volá nějakou uživatelem přiřazenou obsluhu a pak provede své standardní zpracování. Jestliže uživatelova obsluha nastaví Key na nulový znak komponenta přeskočí implicitní zpracování.
Metody komponent se neliší od ostatních metod objektu. Jsou to funkce zabudované ve struktuře třídy komponenty. Obecným pravidlem je minimalizovat počet metod, které uživatel naší komponenty může volat. Lépe než metody je využívat vlastnosti. Vždy, když zapisujeme komponentu minimalizujeme podmínky vkládané na uživatele komponenty. Do nejvyšší míry by měl být uživatel komponenty schopný dělat cokoli s komponentou a kdykoli to chce. Jsou situace, kdy toto nelze splnit, ale našim cílem je nejvíce se k tomu přiblížit. Přestože je nemožné vypsat všechny druhy závislostí, kterým se chceme vyhnout, následující seznam nabízí různé věci, kterým se máme vyhýbat: Metodám, které uživatel musí volat, aby mohl používat komponentu, metodám, které musí být použity v určitém pořadí a metodám, které způsobí, že určité události nebo metody mohou být chybné. Např. když vyvolání metody způsobí, že naše komponenta přejde do stavu, kdy volání jiné metody může být chybné, pak napíšeme tuto jinou metodu tak, že když ji uživatel zavolá a komponenta je ve špatném stavu, pak metoda opraví tento stav před provedením vlastního kódu. Minimálně bychom měli zajistit generování výjimky v případech, kdy uživatel použije nedovolenou metodu. Jinými slovy, jestliže vytvoříme situaci, kde části našeho kódu jsou vzájemně závislé, musíme zajistit, aby používání kódu chybným způsobem nezpůsobilo uživateli problémy. Varování je lepší než havárie systému, když se uživatel nepřizpůsobí našim vzájemným závislostem.
Builder neklade žádná omezení na jména metod a jejich parametrů. Nicméně je několik konvencí, které usnadňují používání metod pro uživatele naších komponent. Je vhodné dodržovat tato doporučení: Volíme popisná jména. Jméno jako PasteFromClipboard je mnohem více informativní než jednoduché Paste nebo PFC. Ve jménech svých funkcí používáme slovesa. Např. ReadFileNames je mnohem srozumitelnější než DoFiles. Jména funkcí by měla vyjadřovat, co vracejí. Přestože funkci vracející vodorovnou souřadnici něčeho můžeme nazvat X, je mnohem výhodnější použít jméno GetHorizontalPosition. Ujistěte se, že metody skutečně musí být metodami. Dobrá pomůcka je, že jméno metody obsahuje sloveso. Jestliže vytvoříme několik metod, jejich jméno neobsahuje sloveso, zamyslete se nad tím, zda by tyto metody nemohly být vlastnostmi.
Všechny části objektů, včetně položek, metod a vlastností mohou mít různé úrovně ochrany. Volba vhodné úrovně ochrany pro metody je velmi důležitá. Všechny metody, které uživatelé našich komponent mohou volat musí být deklarované jako veřejné. Musíme mít na paměti, že mnoho metod je voláno v obsluhách událostí, a tak metody by se měli vyhnout plýtváním systémovými zdroji nebo uvedením Windows do stavu, kdy nemůže reagovat. Konstruktory a destruktory musí být vždy veřejné.
Metody, které jsou implementačními metodami pro komponentu musí být chráněné. To zabrání uživateli v jejich volání v nesprávný čas. Jestliže máme metody, které uživatelský kód nesmí volat, ale objekty potomků volat mohou, pak metody deklarujeme jako chráněné. Např. předpokládejme, že máme metodu která spoléhá na nastavení jistých dat předem. Jestliže tuto metodu uděláme veřejnou, umožníme tím uživateli, aby ji volat i před nastavením potřebných dat. Pokud ale bude chráněná, zajistíme tím, že uživatel ji nemůže volat přímo. Můžeme pak vytvořit jinou veřejnou metodu, která zajistí nastavení dat před voláním chráněné metody.
Jedinou kategorií metod, které musí být vždy soukromé jsou implementační metody vlastností. Tím znemožníme uživateli ve volání metod, které manipulují s daty vlastnosti. Zajišťují, že jediný přístup k těmto informacím je prostřednictvím samotné vlastnosti. Jestliže objekty potomků požadují předefinování metod implementace, mohou to udělat, ale namísto předefinování implementačních metod, musí zpřístupnit zděděnou hodnotu vlastnosti prostřednictvím samotné vlastnosti.
Virtuální metody v komponentách Builderu se neliší od virtuálních metod v jiných třídách. Metodu uděláme virtuální, když chceme aby různé typy byly schopny provést různý kód v reakci na stejné funkční volání. Jestliže vytváříme komponentu určenou pouze k přímému použití koncovým uživatelem, uděláme pravděpodobně všechny její metody statické. Na druhé straně, jestliže vytváříme komponentu více abstraktní povahy, kterou jiní tvůrci komponent mohou použít jako počáteční bod pro své vlastní komponenty, zvážíme vytvoření virtuálních metod. Tímto způsobem, komponenty odvozené od naši komponenty mohou předefinovat zděděné virtuální metody.
Deklarování metod v komponentách se neliší od deklarování metod v jiných třídách. Při deklaraci nové metody v komponentě provedeme dvě věci: přidáme její deklaraci k deklaraci třídy komponenty a implementujeme metodu v souboru CPP jednotky komponenty. Následující kód ukazuje komponentu, která definuje dvě nové metody: jednu chráněnou statickou metodu a jednu veřejnou virtuální metodu.
class TPrikladKomponenty : public TControl
void __fastcall TPrikladKomponenty::Zvetsi()
int __fastcall TPrikladKomponenty::VypoctiOblast()
Windows poskytuje účinné 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. 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. 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í Builderu. Builder ale také tyto grafické funkce zaobaluje a umožňuje tak pracovat při vytváření grafiky produktivněji. 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ě, 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 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 Builder uchovává grafické zdroje, opakovaně použije již existující nástroj pera. Jako příklad, jak jednoduchý grafický kód Builderu může být, následují 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 Builderu.
void TMojeOkno::Paint(TDC& PaintDC, bool erase, TRect& rect)
OldPenHandle=SelectObject(PaintDc,PenHandle);
BrushHandle=CreateSolidBrush(RGB(255,255,0));
OldBrushHandle=SelectObject(PaintDC,BrushHandle);
Ellipse(HDC, 10, 10, 50, 50);
SelectObject(OldBrushHandle);
DeleteObject(BrushHandle);
SelectObject(OldPenHandle);
DeleteObject(PenHandle);
void __fastcall TForm1::FormPaint(TObject *Sender)
Canvas->Brush->Color = clYellow;
Canvas->Ellipse(10, 10, 50, 50);
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ů |
Metody jako MoveTo, LineTo, Rectangle a Ellipse |
Zobrazení a umístění textu |
Metody TextOut, TextHight, TextWidth a TextRect |
|
Vyplňování oblastí |
Metody FillRect a FloodFill |
|
Střední |
Přizpůsobení textu a grafiky |
Vlastnosti Pen, Brush a Font |
Manipulace s body |
Vlastnost Pixels |
|
|
Kopírování a spojování obrázků |
Metody Draw, StretchDraw, BrushCopy a CopyRect a vlastnost CopyMode |
Nízká |
Volání funkcí GDI Windows |
Vlastnost Handle |
Podrobnější informace o objektu plátna a jeho metodách a vlastnostech získáme v nápovědě.
Mnoho práce v Builderu je omezeno na kreslení přímo na plátno komponent a formulářů. Builder také umožňuje zpracovávat standardní obrázky jako jsou bitové mapy, metasoubory a ikony, včetně automatické správy palet. V Builderu jsou tři typy objektů určených pro práci s grafikou: Plátna, která reprezentují bitově mapovanou kreslící plochu na formuláři, grafickém ovladači, tiskárně nebo bitové mapě. Plátno je vždy vlastnost něčeho, ale nikdy to není standardní objekt. Grafika, která reprezentuje grafické obrazy uložené v souborech nebo zdrojích, jako jsou bitové mapy, ikony nebo metasoubory. Builder definuje typy objektů TBitmap, TIcon a TMetafile, všechny odvozené od generického TGraphic. Můžeme také definovat své vlastní grafické objekty. Definováním minimálního standardního rozhraní pro všechnu grafiku, TGraphic poskytuje jednoduchý mechanismus pro aplikace k snadnému použití různých typů grafiky. Obrázky, které jsou kontejnery pro grafiku, mohou obsahovat typy libovolných grafických objektů. Tj. prvek typu TPicture může obsahovat bitovou mapu, ikonu, metasoubor nebo uživatelem definovaný grafický typ a aplikace k nim může přistupovat stejným způsobem prostřednictvím objektu obrázku. Např. ovladač Image má vlastnost nazvanou Picture, typu TPicture umožňující řízení zobrazování obrázků z mnoha typů grafiky. 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.
Všechny obrázky a grafika v 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, např. předáme jméno souboru bitové mapy metodě LoadFromFile obrázku:
void __fastcall TForm1::FormCreate(TObject *Sender)
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.
Když pracujeme na zařízení založené na paletě, pak 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ále realizuje palety. 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č, 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, 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í. Builder provádí jeden krok navíc, realizuje palety pro ovladače v Tab pořadí oken. 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í.
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 redukuje mihotání způsobené opakovaným kreslením přímo na obrazovku. Objekty bitových map v Builderu reprezentující bitově mapované obrázky ve zdrojích a souborech mohou také pracovat jako obrázky 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 plátno obrazovky.
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. Delphi umožňuje č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 |
V nápovědě se můžeme podívat na příklady použití těchto metod.
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. S tímto se seznámíme v následujícím zadání.
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:
#ifndef ShapesH
#define ShapesH
#include <vclSysUtils.hpp>
#include <vclControls.hpp>
#include <vclClasses.hpp>
#include <vclForms.hpp>
class TSampleShape : public TGraphicControl
#endif
#include <vclvcl.h>
#pragma hdrstop
#include 'Shapes.h'
namespace Shapes
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ňuje 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 TSampleShape : public TGraphicControl
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 objektový typ, 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 ;
class 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 specifikuje 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 TSampleShape : public TGraphicControl
Nyní ještě musíme implementovat metodu SetShape. Do souboru CPP jednotky přidáme:
void __fastcall TSampleShape::SetShape(TSampleShapeType Value)
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ředefinování konstruktoru:
class TSampleShape : public TGraphicControl
opětovně deklarujeme vlastnosti Height a Width s jejich novými implicitními hodnotami:
class TSampleShape : public TGraphicControl
__property Width = ;
a do souboru CPP zapíšeme implementaci nového konstruktoru:
__fastcall TSampleShape::TSampleShape(TComponent* Owner)
: TGraphicControl(Owner)
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 objekty pro manipulaci s nimi při návrhu, a potom kopírovat tyto objekty na plátno, když kreslíme. Objekty jako je pero nebo štětec se nazývají vlastněné objekty, protože komponenta je vlastní a je zodpovědná za jejich vytvoření a zrušení. Spravování vlastněných objektů vyžaduje tři kroky: deklaraci položek objektu, deklaraci přístupových vlastností a inicializaci vlastněných objektů. 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 typu ovladače Shape:
class TSampleShape : public TGraphicControl
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 TSampleShape : public TGraphicControl
__property TPen* Pen=;
Potom do souboru CPP jednotky zapíšeme metody SetBrush a SetPen:
void __fastcall TSampleShape::SetBrush(TBrush* Value)
void __fastcall TSampleShape::SetPen(TPen* Value)
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)
přidáme předefinování destruktoru do deklarace objektu komponenty
class TSampleShape : public TGraphicControl
a do souboru CPP zapíšeme nový destruktor:
__fastcall TSampleShape::~TSampleShape()
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 TSampleShape : public TGraphicControl
void __fastcall TSampleShape::StyleChanged(TObject* Owner)
Po provedení těchto změn se komponenta v reakci na změnu pera nebo štětce překreslí.
Základní možností grafického ovladače je schopnost nakreslení svého obrazu na obrazovku. Abstraktní typ TGraphicControl definuje virtuální metodu nazvanou Paint, kterou předefinujeme 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ředefinová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 TSampleShape : public TGraphicControl
void __fastcall TSampleShape::Paint()
switch (FShape)
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í veřejný 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 <vclSysUtils.hpp>
#include <vclControls.hpp>
#include <vclClasses.hpp>
#include <vclForms.hpp>
#include <vclStdCtrls.hpp>
class TDigClock : public TCustomLabel
#endif
#include <vclvcl.h>
#pragma hdrstop
#include 'DigClock.h'
__fastcall TDigClock::TDigClock(TComponent* Owner) : TCustomLabel(Owner)
RegisterClasses(classes, 0);
FTimer = new TTimer(Owner);
FTimer->OnTimer = UpdateClock;
FTimer->Enabled = true;
__fastcall TDigClock::~TDigClock()
void __fastcall TDigClock::UpdateClock(TObject *Sender)
bool __fastcall TDigClock::GetActive()
void __fastcall TDigClock::SetActive(bool Value)
namespace Digclock
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.
Zápis komponenty a jejich vlastností, metod a událostí je pouze částí procesu vytváření komponenty. Musíme ještě umožnit, abychom s komponentou mohli manipulovat při návrhu. To vyžaduje provedení těchto kroků: registrování komponenty v Builderu, přidání bitové mapy komponenty na paletu, poskytnutí nápovědy pro vlastnosti a události a uložení a zavádění vlastnosti. Ne všechny tyto kroky jsou potřebné pro každou komponentu. Např. jestliže nedefinujeme nové vlastnosti nebo události nemusíme k nim vytvářet nápovědu. Nezbytná je vždy pouze registrace. Builder vyžaduje pro seskupování a umisťování komponent na Paletu komponent registraci komponenty. Registrace pracuje na základě programové jednotky, a jestliže vytvoříme několik komponent v jedné jednotce, registrujeme je najednou. S registrací komponent jsme se již seznámili.
Každá komponenta vyžaduje bitovou mapu reprezentující komponentu na Paletě komponent. Jestliže nespecifikujeme svou vlastní bitovou mapu, 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 (Dynamic Component Resource). Tento soubor zdrojů můžeme vytvořit pomocí Editoru obrázků v 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, 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. Pokuste se vytvořit bitovou mapu pro některou komponentu, kterou jste již udělali.
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 stejné 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 Builderu. K vytvoření souboru nápovědy můžeme použít libovolný nástroj. Builder obsahuje Microsoft Help Workshop, který můžeme použít k vytvoření našeho nápovědného souboru. Při vytváření nápovědného souboru je nutno dodržovat tyto konvence: Každá komponenta musí mít obrazovku (obsahuje stručný popis a seznam všech vlastností, událostí a metod dostupných uživateli; obrazovka komponenty musí být označena poznámkou pod čarou K, která obsahuje jméno komponenty, např. TMemo). Každá vlastnost, událost a metoda deklarovaná v komponentě musí mít obrazovku. Každá obrazovka komponenty, vlastnosti, události nebo metody musí mít unikátní ID, které je zadáno jako poznámka pod čarou , název zadaný jako poznámka pod čarou a klíčové slovo používané při hledání jako poznámku pod čarou K.
Builder ukládá formuláře a jejich komponenty v souborech formulářů (DFM). Soubor formuláře je binární reprezentace vlastností formuláře a jeho komponent. Když uživatel Builderu přidává komponenty, zapíše je na jejich formulář, a naše komponenty musí mít schopnost zapsat své vlastnosti do souboru formuláře při uložení. Podobně, když provádíme aplikaci, komponenty se musí sami obnovit ze souboru formuláře. Když vývojář aplikace navrhuje formulář, Builder ukládá popis formuláře do souboru formuláře, který je později připojen k přeložené aplikaci. Když uživatel spustí aplikaci, je přečten tento popis. Popis formuláře obsahuje seznam vlastností formuláře společně s podrobným popisem všech komponent umístěných na formuláři. Každá komponenta, včetně samotného formuláře, je odpovědná za uložení a zavádění svého vlastního popisu. Implicitně při samotném ukládání, komponenta zapíše hodnoty všech svých veřejných a zveřejňovaných vlastností, které se liší od svých implicitních hodnot a to v pořadí jejich deklarací. Při zavádění, se komponenta nejdříve vytvoří sama, pak nastaví všechny vlastnosti na jejich implicitní hodnoty a nakonec čte uložené neimplicitní hodnoty vlastností. Tento implicitní mechanismus slouží mnoha komponentám a nevyžaduje žádnou akci od tvůrce komponent. Nicméně je několik způsobů, jak můžeme přizpůsobit proces ukládání a zavádění potřebný pro naši jistou komponentu.
Komponenty Builderu ukládají hodnoty svých vlastností pouze, jestliže se tyto hodnoty liší od implicitních hodnot. Jestliže nespecifikujeme jinak, Builder předpokládá, že vlastnosti nemají implicitní hodnotu a jejích hodnota je tedy ukládána stále. Vlastnosti jejichž hodnota není nastavena v konstruktoru komponenty mají nulové hodnoty. Nulová hodnota znamená, že paměť vyhrazená pro vlastnost je vynulována. Tj. číselné hodnoty jsou nulové, logické hodnoty jsou nastaveny na false, ukazatele na NULL apod. Ke specifikaci implicitní hodnoty pro vlastnost, přidáme direktivu default a novou implicitní hodnotu na konec deklarace vlastnosti. Můžeme také specifikovat implicitní hodnotu při opětovné deklaraci vlastnosti. Jedním smyslem opětovné deklarace vlastnosti je změna implicitní hodnoty. Specifikace implicitní hodnoty nepřiřazuje automaticky tuto hodnotu vlastnosti při vytváření objektu. Musíme ji ještě přiřadit v konstruktoru objektu. S tím jsme se již seznámili. Můžeme také řídit, zda Builder ukládá každou vlastnost komponenty. Implicitně všechny vlastnosti ve zveřejňované části deklarace objektu jsou ukládány. Můžeme zvolit jejich neukládání nebo navrhnout funkci, která při běhu programu určí zda vlastnost ukládat. K řízení ukládání vlastnosti, přidáme direktivu stored k deklaraci vlastnosti, následovanou true, false nebo jménem metody typu bool. Direktivu stored můžeme použít i v opětovné deklaraci vlastnosti. Následující kód ukazuje komponentu, která deklaruje tři nové vlastnosti. První je vždy ukládána, druhá není ukládána nikdy a třetí je ukládána v závislosti na hodnotě metody
class TPrikladKomponenty : public TComponent
published:
__propetty int Neukladat = ;
__property int NekdyUkladat = ;
Po přečtení všech hodnot vlastností komponenty z uloženého popisu je volána virtuální metoda nazvaná Loaded, která provádí požadované změny v inicializaci. Volání Loaded proběhne před zobrazením formuláře a jeho ovladačů. K inicializaci komponenty po zavedení hodnot jejich vlastností předefinujeme metodu Loaded. První věc, kterou v předefinované metodě Loaded musíme provést je volání zděděné metody Loaded. To zajistí, že všechny zděděné vlastnosti jsou správně inicializovány před provedením inicializace své vlastní komponenty.
Začneme s vývojem další komponenty. Builder poskytuje několik typů abstraktních komponent, které můžeme použít jako základ pro přizpůsobování komponent. V tomto bodě si ukážeme jak vytvořit malý jednoměsíční kalendář na základě komponenty mřížky TCustomGrid. Vytvoření kalendáře provedeme v sedmi krocích: vytvoření a registrace komponenty, zveřejnění zděděných vlastností, změnu inicializačních hodnot, změna velikosti buněk, vyplnění buněk, navigaci měsíců a roků a navigaci dní. Použijeme ruční postup vytváření a registrace komponenty s těmito specifikami: jednotku komponenty nazveme CalSamp, odvodíme nový typ komponenty nazvaný TSampleCalendar od TCustomGrid a registrujeme TSampleCalendar na stránce Samples Palety komponent. Výsledkem této práce je (musíme přidat i hlavičkový soubor Grids.hpp):
#ifndef CALSAMPH
#define CALSAMPH
#include <vclSysUtils.hpp>
#include <vclControls.hpp>
#include <vclClasses.hpp>
#include <vclForms.hpp>
#include <vclGrids.hpp>
class TSampleCalendar : public TCustomGrid
#endif
#include <vclvcl.h>
#pragma hdrstop
#include 'CALSAMP.h'
namespace Calsamp
RegisterComponents('Samples', classes, 0);
}
Abstraktní komponenta mřížky TCustomGrid poskytuje velký počet chráněných vlastností. Můžeme zvolit, které z těchto vlastností chceme zpřístupnit v naši vytvářené komponentě. K zveřejnění zděděných chráněných vlastností, opětovně deklarujeme vlastnosti ve zveřejňované části deklarace naši komponenty. Pro kalendář zveřejníme následující vlastnosti a události:
class TSampleCalendar : public TCustomGrid
Existuje ještě několik dalších vlastností, které jsou také zveřejňované, ale které pro kalendář nepotřebujeme. Příkladem je vlastnost Options, která umožňuje uživatelovi např. volit typ mřížky. Kalendář je mřížka s pevným počtem řádků a sloupců, i když ne všechny řádky vždy obsahují data. Z tohoto důvodu, jsme nezveřejnili vlastnosti mřížky ColCount a RowCount, neboť je jasné, že uživatel kalendáře nebude chtít zobrazovat nic jiného než sedm dní v týdnu. Nicméně musíme nastavit počáteční hodnoty těchto vlastností tak, aby týden měl vždy sedm dní. Ke změně počátečních hodnot vlastností komponenty, předefinujeme konstruktor a nastavíme požadované hodnoty. Musíme také předefinovat čirou metodu DrawCell. Dostaneme toto:
class TSampleCalendar : public TCustomGrid
__fastcall TSampleCalendar::TSampleCalendar(TComponent* Owner)
: TCustomGrid(Owner)
void __fastcall TSampleCalendar::DrawCell(long ACol, long ARow,
const Windows::TRect &Rect, TGridDrawState AState)
Nyní, když přidáme kalendář na formulář má sedm řádků a sedm sloupců s pevným horním řádkem. Pravděpodobně budeme chtít změnit velikost ovladače a udělat všechny buňky viditelné. Dále si ukážeme jak reagovat na zprávu změny velikosti od Windows k určení velikosti buněk.
Když uživatel nebo aplikace změní velikost okna nebo ovladače, Windows zasílá zprávu nazvanou WM_SIZE změněnému oknu nebo ovladači, které tak může nastavit svůj obraz na novou velikost. Naše komponenta může reagovat na tuto zprávu změnou velikosti buněk a zaplnit tak celou plochu ovladače. K reakci na zprávu WM_SIZE, přidáme do komponenty metodu reagující na zprávu. V našem případě ovladač kalendáře vyžaduje k reakci na WM_SIZE přidat chráněnou metodu nazvanou WMSize řízenou indexem zprávy WM_SIZE, a zapsat metodu, která vypočítá potřebné rozměry buněk, což umožní aby všechny buňky byly viditelné (více informací o zpracování zpráv Windows je uvedeno v bodech 30 až 33):
class TSampleCalendar : public TCustomGrid
void __fastcall TSampleCalendar::WMSize(TWMSize &Message)
Nyní, jestliže přidáme kalendář na formulář a změníme jeho velikost, je vždy zobrazen tak, aby jeho buňky úplně zaplnili plochu ovladače. Zatím ale kalendář neobsahuje data.
Ovladač mřížky je zaplňován buňku po buňce. V případě kalendáře to znamená vypočítat datum (je-li) pro každou buňku. Implicitní zaplňování buněk provádíme virtuální metodou DrawCell. Knihovna běhu programu obsahuje pole s krátkými jmény dní a my je použijeme v nadpisu každého sloupce:
void __fastcall TSampleCalendar::DrawCell(long ACol, long ARow,
const TRect &ARect, TGridDrawState AState)
Pro ovladač kalendáře je vhodné, aby uživatel a aplikace měli mechanismus pro nastavování dne, měsíce a roku. Builder ukládá datum a čas v proměnné typu TDateTime. TDateTime je zakódovaná číselná reprezentace datumu a času, která je vhodná pro počítačové zpracování, ale není použitelná pro použití člověkem. Můžeme tedy ukládat datum v zakódovaném tvaru, poskytnout přístup k této hodnotě při běhu aplikace, ale také poskytnout vlastnosti Day, Month a Year, které uživatel komponenty může nastavit při návrhu. K uložení data pro kalendář, potřebujeme soukromou položku k uložení data a vlastnosti běhu programu, které poskytují přístup k tomuto datu. Přidání interního data ke kalendáři vyžaduje tři kroky: V prvním deklarujeme soukromou položku k uložení data:
class TSampleCalendar : public TCustomGrid
V druhém inicializujeme datovou položku v konstruktoru:
__fastcall TSampleCalendar::TSampleCalendar(TComponent* Owner)
: TCustomGrid(Owner)
V posledním deklarujeme vlastnost běhu programu k poskytnutí přístupu k zakódovaným datům. Potřebujeme metodu pro nastavení data, protože nastavení data vyžaduje aktualizaci obrazu ovladače na obrazovce:
class TSampleCalendar : public TCustomGrid
void __fastcall TSampleCalendar::SetCalendarDate(TDateTime Value)
Zakódované datum je vhodné pro aplikaci, ale lidé dávají přednost práci s dny, měsíci a roky. Můžeme poskytnout alternativní přístup k těmto prvkům uložených zakódovaných dat vytvořením vlastností. Protože každý prvek dat (den, měsíc a rok) je celé číslo a jelikož nastavení každého z nich vyžaduje dekódování dat, můžeme se vyhnout opakování kódu sdílením implementačních metod pro všechny tři vlastnosti. Tj. můžeme zapsat dvě metody, první pro čtení prvku a druhou pro jeho zápis, a použít tyto metody k získání a nastavování všech tří vlastností. Deklarujeme tři vlastnosti a každé přiřadíme jedinečný index:
class TSampleCalendar : public TCustomGrid
__property int Month = ;
__property int Year = ;
Dále zapíšeme deklarace a definice přístupových metod, pracujících s hodnotami podle hodnoty indexu:
class TSampleCalendar : public TCustomGrid
int __fastcall TSampleCalendar::GetDateElement(int Index)
return result;
void __fastcall TSampleCalendar::SetDateElement(int Index, int Value)
}
FDate = TDateTime(AYear, AMonth, ADay);
Refresh();
Nyní můžeme nastavovat den, měsíc a rok kalendáře během návrhu použitím Inspektora objektů a při běhu aplikace použitím kódu. Zatím ještě ale nemáme přidaný kód pro zápis datumu do buněk. Přidání čísel do kalendáře vyžaduje několik úvah. Počet dní v měsíci závisí na tom, o který měsíc se jedná a zda daný rok je přestupný. Dále měsíce začínají v různém dni v týdnu, v závislosti na měsíci a roku. V předchozí části je popsáno jak získat aktuální měsíc a rok. Nyní můžeme určit, zda specifikovaný rok je přestupný a počet dní v měsíci. Objektu kalendáře přidáme dvě metody: logickou funkci indikující zda současný rok je přestupný a celočíselnou funkci vracející počet dní v současném měsíci. Obě deklarace metod musíme také přidat do deklarace typu TSampleCalendar.
bool __fastcall TSampleCalendar::IsLeapYear()
int __fastcall TSampleCalendar::DaysThisMonth()
int result;
if ((int)FDate == 0) result = 0;
else
return result;
Funkce DaysThisMonth vrací nulu, jestliže uložené datum je prázdné. To slouží aplikaci k indikování chybného data. Indikace, že měsíc nemá dny, vytvoří prázdný kalendář reprezentující chybné datum. Když již máme informace o přestupných rocích a dnech v měsíci, můžeme vypočítat, kde v mřížce je konkrétní datum. Výpočet je založen na dni v týdnu, kdy měsíc začíná. Protože potřebujeme ofset měsíce pro každou buňku, je praktičtější je vypočítat pouze, když měníme měsíc nebo rok. Tuto hodnotu můžeme uložit v položce třídy a aktualizovat ji při změně data. Zaplnění dnů do příslušných buněk provedeme takto: Přidáme položku ofsetu měsíce a metodu aktualizující hodnotu položky k objektu:
class TSampleCalendar : public TCustomGrid
void __fastcall TSampleCalendar::UpdateCalendar()
Refresh();
Přidáme příkazy do konstruktoru a metod SetCalendarDate a SetDateElement, které volají novou aktualizační metodu při změně data.
__fastcall TSampleCalendar::TSampleCalendar(TComponent* Owner)
: TCustomGrid(Owner)
void __fastcall TSampleCalendar::SetCalendarDate(TDateTime Value)
void __fastcall TSampleCalendar::SetDateElement(int Index, int Value)
Přidáme ke kalendáři metodu, která vrací číslo dne, když předáme souřadnice řádku a sloupce buňky:
int __fastcall TSampleCalendar::DayNum(int ACol, int ARow)
Nesmíme zapomenou přidat deklaraci DayNum do deklarace třídy komponenty. Nyní, když již víme v které buňce které datum je, můžeme doplnit DrawCell k plnění buňky datem:
void __fastcall TSampleCalendar::DrawCell(long ACol, long ARow,
const TRect &ARect, TGridDrawState AState)
Canvas->TextRect(ARect, ARect.Left + (ARect.Right - ARect.Left –
Canvas->TextWidth(TheText)) / 2, ARect.Top + (ARect.Bottom –
ARect.Top - Canvas->TextHeight(TheText)) / 2, TheText);
Jestliže nyní opětovně instalujeme komponentu a umístíme ji na formulář, vidíme správné informace pro současný měsíc.
Nyní, když již máme čísla v buňkách kalendáře, je vhodné přesunout vybranou buňku na buňku se současným datem. Implicitně výběr začíná v levé horní buňce, a tak je potřeba nastavit vlastnosti Row a Col, když vytváříme kalendář a když změníme datum. K nastavení výběru na tento den, změníme metodu UpdateCalendar tak, aby nastavila obě vlastnosti před voláním Refresh:
void __fastcall TSampleCalendar::UpdateCalendar()
Refresh();
Vlastnosti jsou užitečné pro manipulace s komponentami, obzvláště během návrhu. Jsou ale typy manipulací, které často ovlivňují více než jednu vlastnost, a je tedy užitečné pro ně vytvořit metodu. Příkladem takového manipulace je služba kalendáře “následující měsíc”. Zpracování měsíce v rámci měsíců a případná inkrementace roku je jednoduchá, ale velmi výhodná pro vývojáře používající komponentu. Jedinou nevýhodou zaobalení manipulací do metody je, že metody jsou přístupné pouze za běhu aplikace. Pro kalendář přidáme následující čtyři metody pro následující a předchozí měsíc a rok:
void __fastcall TSampleCalendar::NextMonth()
void __fastcall TSampleCalendar::PrevMonth()
void __fastcall TSampleCalendar::NextYear()
void __fastcall TSampleCalendar::PrevYear()
Musíme také přidat deklarace nových metod k deklaraci třídy kalendáře. Nyní, když vytváříme aplikaci, která používá komponentu kalendáře, můžeme snadno implementovat procházení přes měsíce nebo roky.
K daném měsíci jsou možné dva způsoby navigace přes dny. První je použití kurzorových kláves a druhý je reakce na kliknutí myši. Standardní komponenta mřížky zpracovává oboje jako kliknutí. Tj. použití kurzorové klávesy je chápáno jako kliknutí na odpovídající buňku. Zděděné chování mřížky zpracovává přesun výběru v reakci na stisknutí kurzorové klávesy nebo kliknutí, ale jestliže chceme změnit vybraný den, musíme toto implicitní chování modifikovat. K obsluze přesunu v kalendáři, předefinujeme metodu Click mřížky. Když předefinováváme metodu jako je Click, musíme vždy vložit volání zděděné metody, a neztratit tak standardní chování. Následuje předefinovaná metoda Click pro mřížku kalendáře. Nesmíme zapomenout přidat deklaraci Click do TSampleCalendar:
void __fastcall TSampleCalendar::Click()
Nyní, když uživatel může změnit datum v kalendáři, musíme zajistit, aby aplikace mohla reagovat na tuto změnu. Do TSampleCalendar přidáme událost OnChange. Musíme deklarovat událost, položku k uložení události a virtuální metodu k volání události:
class TSampleCalendar : public TCustomGrid
Dále zapíšeme metodu Change:
void __fastcall TSampleCalendar::Change()
Na konec metod SetCalendarDate a SetDateElement musíme ještě přidat příkaz volání metody Change:
void __fastcall TSampleCalendar::SetCalendarDate(TDateTime Value)
void __fastcall TSampleCalendar::SetDateElement(int Index, int Value)
Aplikace používající komponentu může nyní reagovat na změny data komponenty připojením obsluhy k události OnChange.
Když přecházíme po dnech v kalendáři, zjistíme nesprávné chování při výběru prázdné buňky. Kalendář umožňuje přesunutí na prázdnou buňku, ale nemění datum v kalendáři. Nyní zakážeme výběr prázdných buněk. K určení, zda daná buňka je vybíratelná, předefinujeme metodu SelectCell mřížky. SelectCell je funkce, která jako parametry přebírá číslo řádku a sloupce a vrací logickou hodnotu indikující zda specifikovaná buňka je vybíratelná. Metoda SelectCell bude nyní vypadat takto:
bool __fastcall TSampleCalendar::SelectCell(long ACol, long ARow)
Tím jsme dokončili tvorbu naší komponenty. Pokud první den v měsíci je neděle, pak první řádek naší komponenty je prázdný. Pokuste se odstranit tuto chybu.
Když pracujeme s připojenou databází, je často užitečné mít ovladač, závislý na této databázi. Aplikace tedy může založit propojení mezi ovladačem a nějakou částí databáze. Builder obsahuje závislá editační okna, seznamy, kombinovaná okna a mřížky. Můžeme také vytvořit svůj vlastní závislý ovladač. Je několik stupňů závislosti dat. Nejjednodušší je datová závislost pouze pro čtení, nebo prohlížení dat, a umožnit reakci 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ím bodě. Vytvoření ovladače datově závislého 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:
#ifndef DBCalH
#define DBCalH
#include <vclSysUtils.hpp>
#include <vclControls.hpp>
#include <vclClasses.hpp>
#include <vclForms.hpp>
#include <vclGrids.hpp>
#include 'CALSAMP.h'
class TDBCalendar : public TSampleCalendar
#endif
#include <vclvcl.h>
#pragma hdrstop
#include 'DBCal.h'
namespace Dbcal
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 TDBCalendar : public TSampleCalendar
Zapíšeme definici konstruktoru:
__fastcall TDBCalendar::TDBCalendar(TComponent* Owner)
: TSampleCalendar(Owner)
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)
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 TDBCalendar : public TSampleCalendar
bool __fastcall TDBCalendar::SelectCell(long ACol, long ARow)
void __fastcall TDBCalendar::UpdateCalendar()
catch()
FUpdating = false;
Kalendář stále neumožňuje uživateli provádět změny data, ale již správně reaguje na změny data provedené změnou vlastností data. Dále potřebujeme ke kalendáři přidat schopnost prohlížet data.
Propojení mezi ovladačem a databází je obsluhováno objektem nazvaným datový spoj. 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 závislé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 TDBCalendar : public TSampleCalendar
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ě závislý 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 spojení.
class TDBCalendar : public TSampleCalendar
__property TDataSource *DataSource = ;
AnsiString __fastcall TDBCalendar::GetDataField()
TDataSource *__fastcall TDBCalendar::GetDataSource()
void __fastcall TDBCalendar::SetDataField(const AnsiString Value)
void __fastcall TDBCalendar::SetDataSource(TDataSource *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. Závislý 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ředefinujeme tedy konstruktor a destruktor kalendáře k vytváření a rušení objektu datového spoje.
class TDBCalendar : public TSampleCalendar
__fastcall TDBCalendar::TDBCalendar(TComponent* Owner)
: TSampleCalendar(Owner)
__fastcall TDBCalendar::~TDBCalendar()
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.
class TDBCalendar : public TSampleCalendar
__fastcall TDBCalendar::TDBCalendar(TComponent* Owner)
: TSampleCalendar(Owner)
__fastcall TDBCalendar::~TDBCalendar()
void __fastcall TDBCalendar::DataChange(TObject *Sender)
Tím je zobrazovací databázový ovladač kalendáře hotov. Vytvoření editačního ovladače je mnohem komplikovanější. Dříve než budeme pokračovat ve vývoji této komponenty se seznámíme se zpracováním zpráv Windows.
Jedním z klíčů tradičního programování Windows je zpracování zpráv zasílaných z Windows aplikaci. Builder je většinou zpracuje za nás, ale v případě vytváření komponent je možné, že budeme potřebovat zpracovat zprávu, kterou Builder nezpracovává nebo že vytvoříme svou vlastní zprávu a budeme ji potřebovat zpracovat. Všechny objekty Builderu mají mechanismus pro zpracování zpráv. Základní myšlenkou zpracování zpráv je to, že objekt přijme zprávu nějakého typu a zpracuje ji voláním jedné z množiny specifikovaných metod závisejících na přijaté zprávě. Neexistuje-li metoda pro jistou zprávu, je použita implicitní obsluha. Následující diagram ukazuje systém zpracování zpráv:
Událost MainWndProc WndProc Dispatch Obsluha
Knihovna komponent Builderu definuje systém zpracování zpráv, který překládá všechny zprávy Windows (včetně uživatelem definovaných zpráv) určené jistému objektu na volání metod. Tento mechanismus nebudeme nikdy potřebovat měnit. Budeme potřebovat pouze vytvářet metody zpracování zpráv. Zpráva Windows je datový záznam, který obsahuje několik důležitých položek. Nejdůležitější z nich je hodnota, která identifikuje zprávu. Windows definuje mnoho zpráv a soubor MESSAGES.HPP deklaruje identifikátory pro všechny z nich. Další důležité informace ve zprávě jsou obsaženy ve dvou položkách parametrů a položce výsledku. Jeden parametr je 16 bitový a druhý 32 bitový. Jak často vidíme v kódu Windows, odkazujeme se na tyto hodnoty jako na wParam (word parametr) a lParam (long parametr). Často každý z těchto parametrů obsahuje více než jednu informaci a na časti parametrů se odkazujeme makry jako LOWORD a HIWORD. Např. voláním HIWORD(lParam) získáme vyšší slovo tohoto parametru. Původně si programátoři Windows museli pamatovat, co každý parametr obsahuje. Později Microsoft tyto parametry pojmenoval, což usnadňuje jejich používání. Např. parametr zprávy WM_KEYDOWN se nyní nazývá nVirtKey, což je více informativní než wParam.
Builder zjednodušuje systém zpracování zpráv v několika směrech: Každá komponenta dědí kompletní systém zpracování zpráv. Tento systém má implicitní zpracování. Definujeme pouze obsluhy pro zprávy, na které chceme reagovat specificky. Můžeme modifikovat pouze malé části zpracování zpráv a pro většinu zpracování použít zděděné metody. Značnou výhodou tohoto systému zpracování zpráv je, že můžeme bezpečně zasílat kdykoli libovolnou zprávu libovolné komponentě. Jestliže komponenta nemá pro zprávu definovanou obsluhu, pak je použita implicitní obsluha, což obvykle ignoruje zprávu. Builder registruje metodu nazvanou MainWndProc jako proceduru okna po každý typ komponenty v aplikaci. MainWndProc obsahuje blok zpracování výjimek, předávající záznam zprávy z Windows virtuální metodě nazvané WndProc a zpracovávající libovolné výjimky voláním metody HandleException objektu aplikace. MainWndProc je statická metoda, která neobsahuje speciální zpracování některých zpráv. Přizpůsobení provádíme až v WndProc, neboť každý typ komponenty může předefinovat tuto metodu podle svých potřeb. Metody WndProc testují zda zpracovávání nemá ignorovat některé neočekávané zprávy. Např. TWinControl během tažení komponenty ignorují události klávesnice. WndProc předává události klávesnice pouze když komponenta není tažena. Konečně WndProc volá Dispatch, statickou metodu zděděnou od TObject, určující která metoda bude volána k obsluze zprávy. Dispatch používá položku Msg záznamu zprávy k určení jak zpracovat jistou zprávu. Jestliže komponenta pro zprávu nemá obsluhu Dispatch volá DefaultHandler.
Před změnou zpracování zpráv naší komponenty se ujistíme, že to skutečně musíme udělat. Builder překládá mnoho zpráv Windows na události, které jak tvůrce komponenty, tak i uživatel komponenty může obsloužit. Lépe než měnit chování zpracování zpráv je měnit chování zpracování událostí. Ke změně zpracování zprávy předefinujeme metodu zpracovávající zprávu. Můžeme také zabránit komponentě ve zpracování zprávy při jistých situacích zachycením zprávy. Pro změnu způsobu zpracování jisté zprávy komponentou předefinujeme metodu zpracování zprávy pro tuto zprávu. Jestliže komponenta nemá obsluhu pro jistou zprávu, musíme deklarovat novou metodu obsluhy zprávy. K předefinování metody zpracování zpráv, deklarujeme novou metodu v chránění části naši komponenty a to se stejným jménem jako má metoda kterou předefinováváme a mapujeme metodu na zprávu pomocí tří maker. Tato makra mají tvar:
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(parametr1, parametr2, parametr3)
END_MESSAGE_MAP
Parametr1 je index zprávy definovaný Windows, parametr2 je typ struktury zprávy a parametr3 je jméno metody zprávy. Mezi BEGIN_MESSAGE_MAP a END_MESSAGE_MAP můžeme vložit několik maker MESSAGE_HANDLER. Např. k předefinování obsluhy zprávy WM_PAINT v komponentě, opětovně deklarujeme metodu WMPaint a třemi makry mapujeme metodu na zprávu WM_PAINT:
class TMojeKomponenta : public TComponent
Pouze uvnitř metody zpracování zprávy má naše komponenta přístup ke všem parametrům záznamu zprávy. Jelikož zpráva je vždy parametr volaný odkazem, obsluha může změnit v případě potřeby hodnoty parametrů. Často měníme pouze parametr návratové hodnoty zprávy: hodnotu vrácenou voláním SendMessage, která zasílá zprávu. Protože se typ parametru Message metody zpracování zprávy mění s typem zpracovávané zprávy, je nutné se podívat do dokumentace Windows na jména a význam jednotlivých parametrů. Jestliže se z nějakého důvodu potřebujeme odkazovat na parametr zprávy jejich starým stylem jmen (wParam, lParam apod.), můžeme přetypovat Message na generický typ TMessage, který používá tyto jména parametrů. V jistých situacích, může chtít, aby naše komponenta ignorovala jisté zprávy. Tj. můžeme chtít zabránit komponentě od zpracování zprávy svou obsluhou. Zachycení zprávy provedeme předefinováním virtuální metody WndProc. Metoda WndProc filtruje zprávy před jejich předáním metodě Dispatch, která určuje metodu zpracující zprávu. Předefinováním WndProc, můžeme změnit filtr zpráv před jejich zpracováním. Předefinování WndProc provádíme takto:
void __fastcall TMujOvladac::WndProc(TMessage* Message)
Následuje část metody WndProc pro TControl jak je implementována ve VCL v Object Pascalu. TControl definuje rozsah zpráv myši, které jsou filtrovány, když uživatel provádí tažení. Předefinování WndProc tomu pomáhá dvěma způsoby: Můžeme filtrovat interval zpráv namísto specifikování obsluhy pro každou z nich a můžeme zabránit zpracování zpráv v celku a obsluhy nejsou nikdy volány.
procedure TControl.WndProc(var Message: TMessage);
begin
if (Message.Msg>=WM_MOUSEFIRST)and(Message.Msg<=WM_MOUSELAST) then
if Dragging then
DragMouseMsg(TWMouse(Message))
else
end;
end;
Přestože Builder poskytuje obsluhy pro mnoho zpráv Windows, můžeme se dostat do situace, kdy budeme potřebovat vytvořit novou obsluhu zpráv a to když definujeme svou vlastní zprávu. Práce s uživatelskými zprávami má dva aspekty: definování své vlastní zprávy a deklarování nové metody zpracování zprávy. Několik standardních komponent definuje zprávy pro interní použití. Smyslem pro definování zpráv je vysílání informací nepodporované standardními zprávami Windows a oznámení změny stavu. Definování zprávy je dvoukrokový proces: deklarujeme identifikátor zprávy a deklarujeme typ záznamu zprávy. Identifikátor zprávy je celočíselná konstanta. Windows rezervuje zprávy do 1024 pro svoje vlastní použití a tak když deklarujeme svou vlastní zprávu musíme začít nad touto úrovní. Konstanta WM_USER reprezentuje počáteční číslo pro uživatelem definované zprávy. Když definujeme identifikátory zpráv, musíme začít od WM_USER. Musíme si být vědomi, že některé standardní ovladače Windows používají zprávy v rozsahu uživatelských definic. Jsou to seznamy, kombinovaná okna, editační okna a tlačítka. Jestliže odvozujeme komponentu od některé z nich a chceme definovat novou zprávu pro ní, je potřeba se podívat do souboru MESSAGES.HPP a zjistit, které zprávy Windows jsou skutečně definované pro tyto ovladače. Následující kód ukazuje dvě uživatelem definované zprávy:
#define WM_MOJEPRVNIZPRAVA (WM_USER + 400)
#define WM_MOJEDRUHAZPRAVA (WM_USER + 401)
Jestliže chceme dát smysluplná jména parametrům naší zprávy, je potřeba deklarovat typ struktury zprávy pro tuto zprávu. Struktura zprávy je typ předávaného parametru metodě zpracování zprávy. Jestliže nepoužíváme parametr zprávy nebo jestliže chceme použít starý způsob zápisu parametrů (wParam, lParam apod.), můžeme použít implicitní záznam zprávy TMessage. Při deklaraci typu struktury zprávy používáme tyto konvence: Jméno typu záznamu vždy začíná T a následuje jméno zprávy. Jméno první položky v záznamu je Msg a je typu Cardinal. Definujeme další dvě slabiky, které odpovídají wParam. Definujeme další čtyři slabiky, které odpovídají lParam. Nakonec přidáme položku nazvanou Result, která je typu Longint. Následuje záznam zpráv pro všechny zprávy myši, TWMMouse:
struct TWMMouse ;
struct
;
};
Jsou dvě situace, které vyžadují deklarování nové metody zpracování zprávy: naše komponenta vyžaduje zpracování zprávy Windows, která není zpracovávána standardními komponentami a definování své vlastní zprávy pro použití v našich komponentách. Deklaraci metody zpracování zprávy provedeme takto: Deklarujeme metodu v chráněné části deklarace třídy komponenty. Ujistíme se, že metoda vrací void. Nazveme metodu po zpracovávané zprávě, ale bez znaků podtržení. Předáváme jeden parametr volaný odkazem nazvaný Message, typu struktury zprávy. Mapujeme metodu na zprávu použitím maker. Zapíšeme kód pro specifické zpracování v komponentě. Voláme zděděnou obsluhy zprávy. Následuje deklarace obsluhy zprávy pro uživatelem definovanou zprávu nazvanou CM_CHANGECOLOR:
#define CM_CHANGECOLOR (WM_USER + 400)
class TMojeKomponenta : public TControl
Budeme pokračovat ve vývoji předchozí komponenty a umožníme editovat připojenou datovou položku. Protože se jedná o editační ovladač musíme implicitně nastavit jeho vlastnost ReadOnly na false (v konstruktoru i v definici vlastnosti). Nás ovladač musí reagovat na zprávy Windows týkající se myši (WM_LBUTTONDOWN, WM_MBUTTONDOWN a WM_RBUTTONDOWN) a zprávy klávesnice (WM_KEYDOWN). K umožnění, aby ovladač reagoval na tyto zprávy, musíme zapsat obsluhy reagující na tyto zprávy. Chráněná metoda MouseDown je metoda pro událost ovladače OnMouseDown. Ovladač sám volá MouseDown v reakci na zprávu stisknutí tlačítka myši od Windows. Když předefinováváme zděděnou metodu MouseDown, můžeme vložit kód, který poskytuje ostatní reakce voláním události OnMouseDown. Do třídy TDBCalendar přidáme metodu MouseDown:
class TDBCalendar : public TSampleCalendar
a do souboru CPP tuto metodu zapíšeme:
void __fastcall TDBCalendar::MouseDown(TMouseButton Button,
TShiftState Shift, int X, int Y)
Když MouseDown reaguje na zprávu myši, pak zděděná metoda MouseDown je volána pouze, jestliže vlastnost ReadOnly ovladače je nastavena na false a jestliže objekt datového spoje je v editačním režimu (položka může být editována). Jestliže položka nemůže být editována, pak je provedena obsluha události OnMouseDown (existuje-li).
Metoda KeyDown je chráněná metoda pro událost OnKeyDown ovladače. Ovladač sám volá KeyDown v reakci na zprávu stisknutí klávesy od Windows. Obdobně jako MouseDown předefinujeme i KeyDown:
class TDBCalendar : public TSampleCalendar
void __fastcall TDBCalendar::KeyDown(unsigned short &Key,TShiftState Shift)
Jsou dva typy datových změn: změna v hodnotě položky, která musí být zohledněna v ovladači a změna v ovladači, která musí být provedena v datové položce. Komponenta TDBCalendar má metodu DataChange, která zpracovává změny v hodnotě položky a tak první typ změn je již ošetřen. Třída položky datového spoje má událost OnUpdateData, která nastane, když uživatel modifikuje obsah ovladače. Ovladač kalendáře má metodu UpdateData, kterou můžeme použít k obsloužení této události. Přidáme tedy metodu UpdateData do deklarace třídy formuláře:
class TDBCalendar : public TSampleCalendar
do souboru CPP zapíšeme metodu UpdateData
void __fastcall TDBCalendar::UpdateData(TObject *Sender)
a v konstruktoru TDBCalendar přiřadíme metodu UpdateData události OnUpdateData
__fastcall TDBCalendar::TDBCalendar(TComponent* Owner)
: TSampleCalendar(Owner)
Když je nastavena nová hodnota, pak je volána metoda Change ovladače kalendáře. Change volá obsluhu události OnChange (pokud existuje). Uživatel komponenty může zapsat kód obsluhy události OnChange k reagování na změnu datumu. Musíme tedy přidat novou metodu Change do komponenty TDBCalendar
class TDBCalendar : public TSampleCalendar
a zapsat metodu Change, volající metodu Modified, která informuje datový spoj, že datum bylo změněno a potom volá zděděnou metodu Change
void __fastcall TDBCalendar::Change()
Posledním krokem při vytváření editovatelného ovladače je aktualizace datového spoje na novou hodnotu. To nastává, když změníme hodnotu v ovladači a ovladač opustíme (klinutím mimo ovladač nebo stiskem klávesy Tab). VCL má definovány zprávy pro operace s ovladačem. Např. zpráva CM_EXIT je zaslána, když uživatel ovladač opustí. Můžeme zapsat obsluhu zprávy, která na zprávu bude reagovat. V našem případě, když uživatel opustí ovladač, metoda CMExit (obsluha zprávy pro CM_EXIT) reaguje aktualizací záznamu v datovém spoji na změněnou hodnotu. Do komponenty přidáme obsluhu zprávy
class TDBCalendar : public TSampleCalendar
a do souboru CPP zapíšeme:
void __fastcall TDBCalendar::CMExit(TWMNoParams & Message)
catch()
Tím je vývoj editovatelné komponenty hotov.
Často používané komponenty dialogových oken můžeme také přidat na Paletu komponent. Naše komponenta dialogového okna bude pracovat stejně jako komponenty které reprezentují standardní dialogová okna Windows. Vytvoření komponenty dialogového okna vyžaduje čtyři kroky: definování rozhraní komponenty, vytvoření a registraci komponenty, vytvoření rozhraní komponenty a testování komponenty. Cílem je vytvořit jednoduchou komponentu, kterou uživatel může přidat k projektu a nastavit její vlastnosti během návrhu. “Obalovací” komponenta Builderu přiřazená k dialogovému oknu je vytvoří a provede při běhu aplikace a předá data definovaná uživatelem. Komponenta dialogového okna je tedy zase opětovně použitelná a přizpůsobitelná. Dále si ukážeme jak vytvořit obalovou komponentu okolo generického formuláře About obsaženého v Galerii Builderu. Nejprve zkopírujeme soubory ABOUT.H, ABOUT.CPP a ABOUT.DFM do našeho pracovního adresáře. ABOUT.CPP vložíme do nějaké aplikace a provedeme překlad. Tím vytvoříme soubor ABOUT.OBJ, který budeme potřebovat při vytváření komponenty.
Dříve než můžeme vytvořit komponentu pro naše dialogové okno, musíme určit jak chceme, aby ji vývojář používal. Vytvoříme rozhraní mezi našim dialogovým oknem a aplikací, která jej používá. Např. podívejme se na vlastnosti komponenty společného dialogového okna. Umožňují vývojáři nastavovat počáteční stav dialogového okna, jako je titulek a počáteční nastavení ovladačů, a po uzavření dialogového okna převzít zpět požadované informace. Není to přímá interakce s jednotlivými ovladači v dialogovém okně, ale s vlastnostmi v obalové komponentě. Rozhraní tedy musí obsahovat požadované informace, které formulář dialogového okna může zobrazit a vracet aplikaci. Můžeme si představit vlastnosti obalové komponenty jako data přenášená z a do dialogového okna. V případě okna About, nepotřebujeme vracet žádné informace a tedy vlastnosti obalové komponenty obsahují pouze informace požadované k zobrazení v okně. Jsou to čtyři položky dialogového okna About, které aplikace může ovlivnit a poskytneme tedy čtyři vlastnosti typu řetězce.
Obvyklým způsobem vytvoříme komponentu. Zadáme tato specifika: programovou jednotku komponenty nazveme AboutDlg, od TComponent odvodíme nový typ komponenty TAboutBoxDlg a registrujeme vytvářenou komponentu na stránce Samples Palety komponent. Po provedení těchto akcí dostaneme:
#ifndef AboutDlgH
#define AboutDlgH
#include <vclSysUtils.hpp>
#include <vclControls.hpp>
#include <vclClasses.hpp>
#include <vclForms.hpp>
class TAboutBoxDlg : public TComponent
#endif
#include <vclvcl.h>
#pragma hdrstop
#include 'AboutDlg.h'
__fastcall TAboutBoxDlg::TAboutBoxDlg(TComponent* Owner)
: TComponent(Owner)
namespace Aboutdlg
RegisterComponents('Samples', classes, 0);
}
Nyní, když máme vytvořenou komponentu a definované rozhraní mezi komponentou a dialogovým oknem, můžeme implementovat její rozhraní. To provedeme ve třech krocích: vložíme jednotku formuláře, přidáme vlastnosti rozhraní a přidáme metodu Execute. Pro naší obalovou komponentu k inicializaci a zobrazení obaleného dialogového okna musíme přidat jednotku formuláře okna do jednotky obalové komponenty. Vložíme tedy About.h a sestavení s ABOUT.OBJ do hlavičkového souboru komponenty:
#include 'About.h'
#pragma link 'About.obj'
Jednotka formuláře vždy deklaruje instanci typu formuláře. V případě okna About, je typ formuláře TAboutBox a soubor About.h obsahuje následující deklaraci:
extern TAboutBox *AboutBox;
Vlastnosti v obalové komponentě jsou jednodušší než vlastnosti v normální komponentě. Umožňují pouze předávání dat mezi obalovou komponentou a dialogovým oknem. Vložením dat do vlastností formuláře, povolíme vývojáři nastavit data během návrhu pro obal k jejich předání do dialogového okna při běhu aplikace. Deklarace vlastnosti rozhraní vyžaduje dvě další deklarace v typu komponenty: soukromou položku, kterou obal použije k uložení hodnoty vlastnosti a samotnou zveřejňovanou deklaraci vlastnosti, která specifikuje jméno vlastnosti a říká která položka je použita pro uložení. Vlastnosti rozhraní nevyžadují přístupové metody. Používají přímý přístup ke svým datům. Podle konvencí má objektová položka pro uložení hodnoty vlastnosti stejné jméno jako vlastnost, ale na začátku je přidáno písmeno F. Např. k deklaraci vlastnosti rozhraní celočíselného typu nazvané Rok, použijeme:
class TMujObal : public TComponent
Pro dialogové okno About potřebujeme čtyři vlastnosti typu String, po jedné pro jméno produktu, informaci o verzi, autorských právech a komentář:
class TAboutBoxDlg : public TComponent
__property String Version = ;
__property String Copyright = ;
__property String Comments = ;
Když nyní instalujeme komponentu na paletu a umístíme ji na formulář, můžeme nastavovat vlastnosti a tyto hodnoty jsou automaticky předávány s formulářem. Tyto hodnoty jsou pak použity při provádění dialogového okna. Poslední částí rozhraní komponenty je cesta k otevření dialogového okna a vrácení výsledku při jeho uzavření. Komponenty společných dialogových oken používají logickou funkci nazvanou Execute, která vrací true, jestliže uživatel stiskl OK nebo false, když uživatel okno zrušil. Deklarace pro metodu Execute je vždy tato:
class TMujObal : public TComponent
Minimální implementace pro Execute vyžaduje vytvoření formuláře dialogového okna, jeho zobrazení jako modálního dialogového okna a vrácení true nebo false (v závislosti na návratové hodnotě ShowModal). Následuje minimální metoda Execute pro formulář dialogového okna typu TMojeDialOkno:
bool __fastcall TMujObal::Execute()
catch()
DialOkno->Free();
V praxi, bývá více kódu uvnitř bloku výjimky. Před voláním ShowModal, obal nastaví nějaké vlastnosti dialogového okna na základě vlastností rozhraní obalové komponenty. Po návratu z ShowModal, obal pravděpodobně nastaví některé své vlastnosti rozhraní na základě provedení dialogového okna. V případě okna About, použije obalová komponenta čtyři vlastnosti rozhraní k nastavení obsahu formuláře dialogového okna About. Jelikož okno About nevrací žádné informace, není nutno provádět nic po volání ShowModal. Naše metoda Execute bude tedy vypadat takto:
bool __fastcall TAboutBoxDlg::Execute()
catch()
AboutBox->Free();
return Result;
Když instalujeme komponentu dialogového okna, můžeme ji používat stejně jako společná dialogová okna, umístěním na formulář a jejich provedením. Rychlý způsob k otestování okna About je přidat na formulář tlačítko a provést dialogové okna při stisknutí tohoto tlačítka. Např. jestliže vytvoříme dialogové okno, uděláme jeho komponentu a přidáme ji na Paletu komponent, pak můžeme testování provést v těchto krocích: vytvoříme nový projekt, umístíme komponentu okna About a komponentu tlačítka na formulář, dvojitě klikneme na tlačítko (vytvoříme prázdnou obsluhu události stisku tlačítka), do obsluhy události stisku tlačítka zapíšeme následující řádek kódu:
AboutBoxDlg1->Execute();
a spustíme aplikaci. Můžeme také vyzkoušet nastavit různé vlastnosti komponenty.
Když chceme používat stejné dialogové okno
v mnoha aplikacích, obzvláště když všechny aplikace nejsou aplikacemi
Builderu, můžeme vytvořit dialogové okno v DLL. Protože DLL je
samostatný proveditelný soubor, aplikace zapsané v jiných nástrojích než Builderu,
mohou volat stejné DLL. Např. můžeme DLL volat z aplikací
vytvořených v C++, Delphi, Paradoxu nebo dBASE. Protože DLL je
standardní soubor, každá DLL obsahuje hlavičku knihovny komponent (okolo
100K). Můžeme minimalizovat tuto hlavičku vložením několika
dialogových oken do jedné DLL. Vytvoření dialogového okna v DLL
vyžaduje tři kroky: při
dáme funkci rozhraní, modifikujeme projektový
soubor a otevřeme dialogové okno z aplikace. Předpokládejme, že
chceme vytvořit DLL zobrazující následující jednoduché dialogové okno:
Kód pro DLL dialogového okna je tento:
// DLLMAIN.H
#ifndef dllMainH
#define dllMainH
#include <vclClasses.hpp>
#include <vclControls.hpp>
#include <vclStdCtrls.hpp>
#include <vclForms.hpp>
class TYesNoDialog : public TForm
// exportování funkce rozhraní
extern 'C' __declspec(dllexport) bool InvokeYesNoDialog();
extern TYesNoDialog *YesNoDialog;
#endif
// DLLMAIN.CPP
#include <vclvcl.h>
#pragma hdrstop
#include 'dllMain.h'
#pragma resource '*.dfm'
TYesNoDialog *YesNoDialog;
__fastcall TYesNoDialog::TYesNoDialog(TComponent* Owner)
: TForm(Owner)
void __fastcall TYesNoDialog::YesButtonClick(TObject *Sender)
void __fastcall TYesNoDialog::NoButtonClick(TObject *Sender)
bool __fastcall TYesNoDialog::GetReturnValue()
// exportování standardní C++ funkce rozhraní, kterou voláme mimo VCL
bool InvokeYesNoDialog()
Kód v tomto příkladě zobrazuje dialogové okno a ukládá hodnotu určující stisknuté tlačítko do soukromé položky returnValue. Tuto hodnotu můžeme získat veřejnou funkcí GetReturnValue. K zobrazení dialogového okna a určení které tlačítko bylo stisknuto volá aplikace exportovanou funkci InvokeYesNoDialog. Tato funkce je deklarována v DLLMAIN.H jako exportovaná funkce C (k zabránění komolení jmen C++) a používající standardní volací konvence C. Funkce je definována v DLLMAIN.CPP. To umožňuje aby tato funkce umístěná v DLL byla volána libovolnou aplikací (nejen aplikací vytvořenou v Builderu). Po vytvoření funkce rozhraní pro dialogové okno, musíme ještě modifikovat projektový soubor, aby vytvořil DLL namísto aplikace. K přeložení a sestavení DLL z IDE Builderu, nastavíme volbu Application Target na stránce Linker okna Project Option na Generate DLL a normálně projekt přeložíme.
Po dokončení zabalení našeho dialogového okna do DLL a překladu DLL, můžeme již používat dialogové okno z aplikací. K použití dialogového okna z DLL provést dvě věci: importovat funkci rozhraní z DLL a volat funkci rozhraní.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1347
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved