Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
BulgaraCeha slovacaCroataEnglezaEstonaFinlandezaFranceza
GermanaItalianaLetonaLituanianaMaghiaraOlandezaPoloneza
SarbaSlovenaSpaniolaSuedezaTurcaUcraineana

BiologieBudovaChemieEkologieEkonomieElektřinaFinanceFyzikální
GramatikaHistorieHudbaJídloKnihyKomunikaceKosmetikaLékařství
LiteraturaManagementMarketingMatematikaObchodPočítačůPolitikaPrávo
PsychologieRůznéReceptySociologieSportSprávaTechnikaúčetní
VzděláníZemědělstvíZeměpisžurnalistika

Úvod do vytváření komponent

počítačů



+ Font mai mare | - Font mai mic



DOCUMENTE SIMILARE

TERMENI importanti pentru acest document

Úvod do vytváření komponent

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 ob­rá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í vlast­nosti a události nezbytné pro práci komponenty v Builderu. Různé větve knihovny poskytují další více spe­cia­lizo­vané komponenty. Vytvářenou komponentu přidáme do VCL odvozením nového objektu od někte­ré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 ob­jektu. Jsou dva hlavní rozdíly mezi vytvářením komponent a používáním komponent. Při vytváření kompo­nent 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 kom­ponent koncovými uživateli.

Komponenty jsou prvky programu s nimiž manipulujeme během návrhu. Vytváření nové kom­ponenty znamená odvození typu objektu nové komponenty od existujícího typu. V následující tabulce jsou uve­deny 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 for­mulář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. No­vou 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řípa­dech 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 vlast­nost z typu předka, musíme svou komponentu odvodit od něčeho výše v hierarchii než je TListBox. Nemu­síme začít od typu prázdného abstraktního ovladače a vytvářet všechny funkce seznamu, neboť VCL po­skytuje TCustomListBox, který implementuje všechny vlastnosti potřebné pro seznam, ale všechny je ne­zveřej­ňuje. Když odvozujeme komponentu od některého z abstraktních typů, jako je TCustomListBox, zve­řej­ní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 praco­vat. 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 Win­dows API (Windows potřebuje madlo k určení okna, se kterým má operovat). Jestliže náš ovladač nepotře­buje 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í za­ost­ř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í grafic­ký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 pro­gramo­vá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ý ovla­dač) 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 apli­kací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 pro­gramové 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 kom­ponenty. Nevizuální kompo­nenty 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 for­mulář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 pro­gramu (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á zjed­noduš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řed­pokládá, že různí uživatelé je použijí ve svých aplikacích v rozličných kombi­nací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áves­nice) a kódem zapsaným uživatelem komponenty (obsluha události). Událost je mož­nost poskytnutá tvůrcem kompo­nenty 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ředdefinova­nou událost a ne­musí odvozovat vlastní komponentu. Metody jsou funkce zabudované v komponentě. Uži­vatel komponenty používá metody k přikázání komponentě, aby provedla specifickou akci nebo vrátila jistou hodnotu, která není vlastností. Me­tody jsou také užitečné pro aktualizaci několika svázaných vlastností je­diný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 gra­fickou aplikaci Windows, musíme se seznámit s typy požadavků grafického roz­hraní 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 uvol­něním. Když pracujeme s grafikou v Builderu, nemusíme tyto věci znát. Pro kreslení na formulář nebo kom­po­nentu, 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 Win­dows, ale náš kód bude jednodušší a bude pracovat rychleji, jestliže použijeme plátno zabudované do kom­ponent 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, mu­síme komponentu, která má alespoň minimální funkčnost instalovat na Paletu komponent. Potom lze kom­ponentě 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ě hla­vič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 logic­kých skupin kom­ponent) má také svou vlastní jednotku. Když vytváříme komponentu, můžeme pro kompo­nentu vytvořit novou jednotku nebo přidat novou komponentu do existující jednotky. K vytvoření jednotky pro kompo­nentu, zvolíme File | New Unit. Builder vytvoří nový soubor a otevře jej v Editoru kódu. Vytvořenou jed­notku uložíme pod smysluplným jménem. Pro přidání komponenty k existující jednotce, zvolíme File | Open a vy­bereme zdrojový kód existující jednotky. Když přidáváme komponentu k existující jednotce, mu­síme si být jisti, že jednotka obsahuje pouze kód komponent. Přidání kódu komponent k jednotce, která ob­sahuje např. formulář, způsobí chybu. Jestliže máme novou nebo existující jednotku pro naši kom­ponentu, můžeme od­vodit objekt kompo­nenty. Každá komponenta je třída odvozená od typu TComponent, od jed­noho z více specializovaných po­tomků, jako je TControl nebo TGraphicControl, nebo od existujícího typu kompo­nenty.

K odvození třídy komponenty, přidáme deklaraci třídy do hlavičkového souboru jednotky, která má kom­ponentu 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ši­nou 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 kompo­nentu. Každá nová komponenta musí mít konstruktor, který přepisuje konstruktor třídy, od které kompo­nentu 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 kom­ponent 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 zjed­nodu­š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 kdy­by­chom 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í libovol­ný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 for­muláře. Přidání jména kompo­nenty, která neexistuje na formuláři, vytvoří chybný soubor formuláře.

V konstruktoru formuláře vytvoříme komponentu. Když voláme konstruktor komponenty, mu­sí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í ovla­dač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 pojmeno­vané NovyCtrl. Vytvoříme nový projekt a provedeme výše popsané kroky. Dostaneme tuto jednotku formu­lář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 Li­brary), 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 genero­vané 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 sou­bory: 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 Com­ponent | 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 clas­ses. 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 kom­ponent a můžeme je používat ve svých aplikacích. Paletu komponent můžeme modifikovat volbou Compo­nent | 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í no­vých komponent, pracujeme s objekty způsobem, který koncový uživatel nikdy nepotřebuje. Před zaháje­ním tvorby komponent, je nutno se dobře seznámit s principy objektově orientovaného programování po­psanými dále. Základní rozdíl mezi uživatelem komponent a tvůrcem komponent je ten, že uživatel mani­puluje 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, kte­rou se vyhneme opakování a přidání nových možností k typu. V obou případech, je cílem vytvoření opako­vaně použitelných objektů. Ve všech programovacích úlohách se snažíme vyhnout se opakování. Jestliže potře­bujeme několikrát zapsat stejné řádky kódu, pak je můžeme umístit do funkce nebo dokonce vytvo­řit knihovnu funkcí, kte­rou můžeme používat v mnoha programech. Stejný důvod je i pro komponenty. Jest­liže často měníme stejné vlastnosti nebo provádíme stejné volání metod, je užitečné vytvořit nový typ kom­ponenty, který tyto věci pro­vede 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 opako­vá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 kompo­nenty (např. vytvoření specializova­né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ší pod­množ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 kompo­nenty. 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 stan­dardní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 kompo­nenty svou komponentu odvodíme (viz výše). Builder poskytuje několik abstraktních typů komponent urče­ných pro tvůrce komponent k odvozování nových typů komponent. K deklarování nového typu kompo­nenty, přidáme deklaraci typu do hlavičkového souboru jednotky komponenty. Následující příklad je dekla­race jednoduché gra­fické komponenty:

class TPrikladTvaru : public TGraphicControl

K dokončení deklarace komponenty vložíme do třídy deklarace vlastností, položek a metod, ale prázdná de­kla­race 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 ne­musí 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 kompo­nenty. 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ý ob­jekt 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ž od­vozu­jeme 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 no­vé 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 ne­může odstranit nic z toho co zdědila. Např. jestliže třída má jistou vlastnost, pak všechny přímí nebo ne­pří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řistu­povat ke které části třídy. Specifikací úrovní přístupu, definujeme rozhraní naší komponenty. Pokud ne­specifiku­jeme jinak, pak položky, metody a vlastnosti přidané do naší třídy jsou soukromé. Následující tabulka uka­zuje ú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ži­vateli kompo­nent. Jelikož uživatelé objektu nemohou přistupovat k soukromé části, můžeme změnit vnitřní im­plemen­taci 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 je­jí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řístup­ný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 pro­gramu pro všechen kód a veřejné části třídy definují v tomto objektu rozhraní běhu programu. Roz­hraní běhu pro­gramu je užitečné pro prvky, které nezpracováváme v době návrhu, jako jsou vlastnosti, které závisí na ak­tuálních informacích o typech za běhu programu nebo které jsou určeny pouze pro čtení. Me­tody, které slouží pro uživa­tele našich komponent také deklarujeme jako část rozhraní běhu programu. Po­zname­nejme, ž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 In­spek­tor objektů může přistupovat k vlastnostem a událostem. Protože pouze zveřejňovaná část je zobrazo­vá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 ob­jektu může chtít přizpůsobit během návrhu, ale nesmí obsahovat vlastnosti, které závisí na informacích o pro­stř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í me­tody. 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 vy­tvář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ře­kladu. Základní výhodou statických metod je, že jejich vyřízení je velmi rychlé. Protože překladač může ur­čit adresu me­tody, metoda je volána přímo. Virtuální metody používají nepřímé hledání ad­resy je­jich metod při běhu programu, což je mnohem delší. Další rozdíl u statické metody je ten, že se ne­mění v odvozených typech. Tj. když deklarujeme třídu, která obsahuje statickou metodu, potom od­vozením nové třídy, potomek třídy sdílí přesně stejnou metodu na stejné adrese. Statické me­tody tedy vždy provádějí to samé, bez ohledu na aktuální typ ob­jektu. Statickou metodu nelze předefinovat. Deklarováním metody v typu potomka se stej­ným jménem jako má statická metoda v objektu předka se na­hradí metoda předka. Např. v následujícím kódu první komponenta deklaruje dvě statické metody. Druhá deklaruje dvě sta­tické 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ší. Vir­tuální metody umožňují předefinování v objektech potomků, ale stále metodu voláme stejným způso­bem. Ad­resa 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 vy­tváří 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 deklarova­ných v novém objektu. Potomek třídy může předefinovat některé ze zděděných virtuálních metod. Přede­finování metody znamená její rozšíření nebo změnu, namísto jejího nahrazení. Třída po­tomka může opětovně dekla­rovat a implementovat libovolnou z metod deklarovaných ve svých předcích. Static­kou me­todu nelze pře­definovat, neboť deklarace statické metody se stejným jménem jako má zděděná statická me­toda, 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 jednodu­chých kompo­nent. První deklaruje dvě metody, každá je jiného typu vyřízení. Druhá odvozená od první, nahrazuje static­kou metodu a předefinovává virtuální me­todu.

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í kompo­nenty 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ší po­užít parametr volaný hod­notou než odkazem. Objekty jsou ve skutečnosti uka­zateli, které jsou již odkazem. Předáním objektu odka­zem 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. Vlast­nosti 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. Nej­zř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 hod­noty 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 apli­kace. 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 mo­hou 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 vlast­nosti 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 sa­motné. Efekt nastavení vlastnosti není omezen na nějakou komponentu. Nastavení vlast­nosti Down kompo­nenty urychlovacího tlačítka na true, způsobí nastavení Down všech ostatních urychlo­vacích tlačítek ve skupině na false. Implementace metody pro vlastnost může být virtuální, což zna­mená, ž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 regis­traci komponenty můžeme specifikovat různé editory vlastností. V následující ta­bulce je uvedeno jak vlast­nost 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 defino­vána ve zdrojovém kódu. Uživatel může cyklicky procházet možnými hodnotami dvo­jitý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í. Nic­méně, jestliže objekt, který je vlastností má také zveřejňované vlastnosti, pak In­spektor 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 exis­tují­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 po­tomka. Jestliže odvozujeme komponentu od TWinControl, komponenta např. zdědí vlast­nost 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řej­nou 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, specifiku­jeme 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ší kom­ponenty. 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 de­kla­race typu objektu kompo­nenty, což umožní číst a nastavovat vlastnosti z vnějšku komponenty při běhu pro­gramu. 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á dekla­race 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 mo­hou 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 vlast­nosti mohou přistupo­vat k datům této vlastnosti. Jestliže metoda nebo jiná vlastnost potřebuje změnit tato data, musí to provést pro­střednictvím vlastnosti a ne přímým přístupem k uloženým datům. To zajišťuje, že implementaci zděděné me­tody 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 vlast­nosti specifikují, že přiřazení nebo čtení hodnoty vlastnosti probíhá přímo s položkou vnitřního uložení bez vo­lá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é hod­noty 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 im­plementace 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é za­jistí, že uživatelé komponent nemohou tyto metody volat k modifikaci vlastnosti. Čtecí metoda pro vlast­nost 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ře­dává jejich indexy jako parametry. Čtecí metoda pracuje s vnitřním uložením dat a vytváří hodnotu vlast­nosti příslušného typu. Je-li vlastnost typu “pouze pro zápis”, není nutné deklarovat čtecí metodu. Vlast­nosti, 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 kon­vencí 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ásle­duje příklad zápisové metody pro vlastnost typu int nazva­nou 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í hod­nota 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 ne­specifikujeme, 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í impli­citní hodnoty v deklaraci vlastnosti nenastavu­jeme aktuálně vlastnost na tuto hodnotu. Jako tvůrce kompo­nenty musíme zajistit, že konstruktor komponenty nastaví vlastnost na tuto hodnotu. Když opakovaně de­klarujeme 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í kom­ponenty, která impli­citně neprovádí lámání slov a má žluté pozadí. Je to velmi jednoduchý příklad, ale ukazuje vše, co je za­potř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 (au­tomatický přechod na další řádek při dosažení konce řádku) můžeme snadno vy­tvoř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é kom­ponenty začíná stejně: vytvoříme progra­movou jednotku, odvodíme třídu kom­ponenty, regis­trujeme ji a in­stalujeme ji na Paletu komponent. V našem příkladě použijeme uvedený obecný postup s těmito specifikami: jednotku komponenty na­zveme 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 kom­ponenty nastavují své hodnoty vlastností při vytváření. Když umístíme komponentu bě­hem návrhu na for­mulář nebo když spustíme aplikaci vytvářející komponentu a přečteme její vlastnosti ze sou­boru 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 hod­notami vlastností, aplikace dále nastavuje vlastnosti změněné při návrhu a když je komponenta zobrazena vi­díme ji tak, jak jsme ji na­vrhli. 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ředefinu­jeme konstruktor, pak nový konstruktor musí vždy volat zděděný konstruktor a to dříve než provedeme co­koliv 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ředefi­nované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 vlast­nosti, musíme také určit, že hodnota je implicitní. Jestliže to neprovedeme, Builder nemůže ukládat a obnovovat hod­notu vlastnosti. Když Builder ukládá popis formuláře do souboru formu­láře, ukládá pouze hodnoty vlastností, které se liší od jejich implicitních hodnot. Má to dvě výhody: zmen­šuje to soubor for­muláře a urychluje zavádění for­muláře. Jestliže vytvoříme vlastnost nebo změníme im­plicitní hodnotu vlastnosti je vhodné aktualizovat dekla­raci vlastnosti na novou implicitní hodnotu. Ke změně implicitní hodnoty vlastnosti, opětovně deklarujeme vlastnost a připojíme direktivu default s novou implicitní hodno­tou. Není nutno opětovně deklarovat prvky vlastnosti, pouze jméno a implicitní hod­notu. 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 ex­pli­citně nastavit implicitní hodnotu v konstruktoru komponenty. Rozdíl je ve vnitřní práci aplikace: Builder ne­zapisuje 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 kom­ponenty 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 prove­deme přiřazení). Vyzkou­šejte provést tuto změnu. Nyní již tuto komponentu můžeme používat bez pro­blé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 na­zveme TabList a předefinujeme v ní metodu CreateParams, kterou Builder používá ke změně některých standard­ních hodnot používaných při tvorbě komponenty. Tato metoda bude vypadat takto:

void __fastcall TTabBox::CreateParams(Controls::TCreateParams &Params)

Komponentu vyzkoušejte.

Další informace o komponentách

Některé vlastnosti mohou být indexované, podobně jako pole. Mají více hodnot, které rozlišujeme in­dexem. Příkladem ve standardních komponentách je vlastnost Lines komponenty Memo. Lines je indexovaný se­znam ř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 specifiko­vat 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 roz­dí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 al­ternativní 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ů: zobrazo­vat 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ů: odvo­zení 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 TPropetry­Editor. Když vytváříme editor vlastností, můžeme třídu editoru vlastností odvodit přímo od TPropertyEdi­tor 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 spe­cializované editory vlastností používané pro uni­ká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é, zna­kové a výčtové vlastnosti).

TIntegerProperty

Všechny celočíselné typy včetně předdefinovaných a uživatelem defino­vaných in­tervalů.

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í kompo­nent, 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 ob­jektů. Mnoho vlastností také zpřístupňuje uživateli typ v nové hodnotě pro vlastnost. Objekt editoru vlast­ností poskytuje virtuální metody, které můžeme předefinovat pro převod mezi textovou reprezentací a aktu­ální hod­notou. Tyto metody jsou nazvané GetValue a SetValue. Náš editor vlastností také dědí množinu metod používa­ný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 me­todu 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 hod­not pro vlastnost. Implicitně GetValue vrací ’unknown’. K poskytnutí řetězcové reprezentace pro naši vlastnost, předefinujeme metodu GetValue editoru vlast­ností. Jestliže vlastnost nemá řetězcovou hodnotu, naše GetValue musí převést hodnotu na její řetězcovou re­prezentaci. 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 kte­rou uživatel může otevřít dialogové okno k volbě všech atributů písma. K poskytnutí dialogového okna celko­vého editoru vlastností, předefinujeme metodu Edit třídy edi­toru vlastností. Metoda Edit používá stejné metody “Get” a “Set” jako jsou použity v metodách GetValueSetValue (metoda Edit volá obě tyto me­tody). 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 ob­jektů 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 hod­notu, 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 se­znam možných hodnot. Pro specifikaci atributů editoru, předefinujeme metodu GetAttributes objektu edi­toru. GetAttributes je metoda vracející množinu hodnot typu TPropertyAttributes, která může obsaho­vat 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 se­znamu a editorem. Metoda GetAttributes TColorProperty, tedy obsahuje několik atributů ve své návra­tové hodnotě:

Virtual __fastcall TPropertyAttributes TColorProperty::GetAttributes()

Vytvořený editor vlastností se musí v Builderu registrovat. Registrací editoru vlastností přiřadíme typ vlast­nosti k editoru vlastnosti. Můžeme registrovat editor se všemi vlastnostmi daného typu nebo s jistou vlast­ností 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 vo­lá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 vlast­nosti da­ného typu. Třetím parametrem je jméno vlastnosti. Tento parametr má význam pouze, jestliže předchozí pa­rametr specifikuje konkrétní typ kompo­nenty. V tomto případě, můžeme specifikovat jméno konkrétní vlastnosti v typu komponenty, se kte­rou tento editor pracuje. Posledním parametrem je typ edi­toru 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ší. Re­gistruje editor vlastností TComponentProperty pro všechny vlastnosti typu TComponent (nebo po­tomků 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ý para­metr použijeme NULL a jako třetí parametr prázdný řetězec. Druhý příkaz je nejspecifičtějším typem regis­trace. Registruje editor pro jistou vlastnost v jistém typu komponenty. V tomto případě je to editor pro vlast­nost Name všech kompo­nent. Třetí příklad je specifičtější než první, ale není limitován jako druhý. Regis­truje editor pro všechny vlast­nosti 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 sa­motného ob­jektu. Jako tvůrce komponenty, použijeme události k povolení vývojáři aplikace přizpůsobit chování kom­ponenty. Události pro mnoho akcí uživatele (např. akce myši) jsou zabudovány ve všech standardních kom­po­nentách Builderu, ale můžeme také definovat nové události. Builder implementuje události jako vlast­nosti.

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 kompo­nenty, událost je jméno svázané s událostí systému, jako je např. OnClick, kterému uživatel může přiřa­dit 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živa­telem pro tuto událost. Závěr ob­hospodař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ář obsa­hují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 vo­lá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 ob­jektu 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ě, libo­volný kód použitý jako obsluha události musí být odpovídajícím typem metody třídy. Všechny metody ob­sluh událostí jsou funkce typu void. Jsou kompatibilní s událostí daného typu, metoda ob­sluhy 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 (po­kud vyhovuje) nebo definovat svůj vlastní. Přestože obsluha události je funkce, nesmíme hodnotu funkce ni­kdy použít při zpracování události (funkce musí být typu void). Prázdná funkce vrací nedefinovaný vý­sle­dek, 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 pro­střednictvím parametrů volaných odka­zem. Příkla­dem 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í, indi­kují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 kom­po­nenty 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ři­pojí 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 impli­citně chráněné, což znamená, že koncový uživatel k nim nemůže připojit obsluhu. Když vytvoříme ovla­dač, můžeme zvolit, zda bude událost viditelná pro uživatele našeho ovladače. Jsou dvě kategorie stan­dardních událostí: udá­losti definované pro všechny ovladače a události defino­vané pouze pro standardní okenní ovladače. Nejzá­kladnější události jsou definovány v typu objektu TControl. Všechny ovladače (okenní, gra­fické nebo uži­vatelské) dědí tyto události. Následuje seznam událostí přístupných ve všech ovla­dačí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 od­po­ví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ě deklaro­vat vlastnost události jako veřejnou nebo zveřejňovanou. Opětovná deklarace vlastnosti bez specifikace její imple­mentace 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­řa­dí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 me­tody, 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é kompo­nenty na kliknutí. Namísto přiřazení obsluhy k události OnClick, jak by to provedl uživatel komponenty, předefi­nujeme 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ři­pojen 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 zpra­cová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 specific­ký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é infor­mace o ní. Oznámení používá typ TNoti­fyEvent, 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 klik­nutí 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 ob­sluhy, který obsahuje parametry s nějakými nezbytnými informacemi o události. Jestliže naše událost je genero­vá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í para­metru 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, OnKeyUpOnKeyPress) 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ža­duje 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á. Kompo­nenty 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 nastave­ní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í zpra­co­vá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 kom­ponenty. Obecným pravidlem je minimalizovat počet metod, které uživatel naší kom­ponenty může volat. Lépe než metody je využívat vlastnosti. Vždy, když zapisujeme komponentu minimali­zujeme 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ásledu­jící se­znam 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 prove­de­ním vlastního kódu. Mini­málně bychom měli zajistit generování výjimky v případech, kdy uživatel pou­žije nedovolenou me­todu. Ji­ný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 sys­té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í: Vo­lí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ř. ReadFi­le­Names 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 me­tody skutečně musí být metodami. Dobrá pomůcka je, že jméno metody obsahuje slo­veso. 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é. Mu­sí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ýt­váním systémovými zdroji nebo uvedením Windows do stavu, kdy nemůže reagovat. Konstruktory a de­struktory 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ži­vateli v jejich volání v nesprávný čas. Jestliže máme metody, které uživatelský kód nesmí volat, ale objekty po­tomků 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 zne­mož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í vo­lání. Jestliže vytváříme komponentu určenou pouze k přímému použití koncovým uživatelem, uděláme prav­dě­podobně všechny její metody statické. Na druhé straně, jestliže vytváříme komponentu více abs­traktní po­vahy, kterou jiní tvůrci komponent mohou použít jako počáteční bod pro své vlastní komponenty, zvážíme vy­tvoření virtuálních metod. Tímto způsobem, komponenty odvozené od naši komponenty mohou předefi­novat 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 me­todu 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í. Na­neš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 gra­fiku. Builder se za nás stará o GDI, což dovoluje strávit více času produktivní prací a nemusíme hledat ztra­cená madla nebo neuvolněné zdroje. Builder se stará o vedlejší úkoly za nás a tak se můžeme zaměřit na pro­duktivní a tvořivou činnost. Funkce GDI můžeme také volat přímo z aplikací Builderu. Builder ale také tyto grafické funkce zaoba­luje 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 zobra­zení ob­razu komponenty na obrazovce. Když voláme funkce GDI přímo, musíme mít madlo kon­textu za­řízení, ve kte­rém můžeme vybírat různé kreslící nástroje jako jsou pera, štětce a písma. Po dokreslení na­šeho grafic­kého obrazu, musíme obnovit kontext zařízení do jeho původního stavu. Namísto používání této nízké gra­fické úrovně, Builder poskytuje jednoduché ale kompletní rozhraní: vlastnost Canvas naší kom­ponenty. 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 obhospo­dařo­vání grafických zdrojů v Builderu je to, že může uschovat zdroje pro poz­dější použití, což může značně urychlit opakování operací. Např. jestliže máme program, který opakovaně vy­tváří, použije a uvolní jistý typ nástroje pera, není stále nutné opakovat tyto kroky. Protože Builder uchovává grafické zdroje, opa­kovaně použije již exis­tují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 za­psané 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ě auto­ma­tické správy palet. V Builderu jsou tři typy objektů určených pro práci s grafikou: Plátna, která re­prezentují 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 defi­nuje typy ob­jektů 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ů gra­fiky. Ob­rázky, které jsou kontejnery pro grafiku, mohou obsahovat typy libovolných grafických objektů. Tj. pr­vek 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ů gra­fiky. Objekt obrázku má vždy grafiku a grafika má plátno (plátno má pouze TBitmap). Normálně, když pra­cujeme s obrázkem, pak pracujeme pouze s částí grafického objektu přístupného pro­stř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 ji­ný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 SaveTo­File 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če­ný 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ř. kompo­nenta 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 ob­hospodařování palet, jiných než bitových map. Jestliže máme paletu, kterou chceme použít na ovladač, mu­síme říci naši aplikaci aby použila tuto pa­letu. Pro specifikaci palety pro ovladač, předefinujeme metodu GetPalette ovladače, aby vracela madlo požado­vané 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á odpo­věd­nost za reakce na zprávy palety od Windows. Metoda provádějící správu palet je PaletteChanged. Pro nor­má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 pa­letu popředí.

Když kreslíme složitější obrázek obecnou technikou v programování Windows, vytvoříme neobra­zovkovou bitovou mapu, nakreslíme na ní obrázek a potom překopírujeme kompletní obraz na defini­tivní místo na obrazovce. Použití obrázku neobrazovkové bitové mapy redukuje mihotání způsobené opakova­ným kresle­ním přímo na obrazovku. Objekty bitových map v Builderu reprezentující bitově mapované ob­rázky ve zdrojích a souborech mo­hou 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 zobra­zováno na obrazovce. Namísto kreslení na plátno formuláře nebo ovladače, můžeme vytvořit objekt bitové mapy, kres­lit 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 zdrojo­vé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žado­vané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řa­dí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 mani­pulovat 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á kompo­nenta je identická, je nutno ji nazvat jinak, aby nedošlo k duplikaci identifikátorů. Naši komponentu na­zveme TSampleShape. Vytvoření grafické komponenty vyžaduje tři kroky: vytvoření a registraci kompo­nenty, 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 na­zveme Shapes, odvodíme nový typ komponenty TSampleShape od TGra­phicControl 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é vlast­nosti, můžeme naši komponentě přidat požado­vané grafické mož­nosti. Při vytváření grafického ovladače vždy provádíme tyto dva kroky: určíme co kres­lit a nakreslíme obraz komponenty. Dále pro náš příklad musíme při­dat 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 speci­fikované podmínky. Grafická kompo­nenta, 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ř. ovla­dač 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ž pro­vedeme 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 nej­prve deklaro­vat typ vlastnosti a teprve potom objektový typ, který obsahuje vlastnost. Většina uživatelem defino­vaný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 vlast­nosti (čtení hodnoty provádíme často přímo). Pro náš ovladač deklarujeme položku pro uložení aktuál­ního tvaru a deklarujeme vlastnost, která čte tuto položku a zapisuje ji prostřednictvím volání metody. Při­dá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. Impli­citní 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řede­finování kon­struktoru:

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 ob­jektů 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ě. Po­ložka zajišťuje, že komponenta má vždy ukazatel na vlastněný objekt a může tak před svým zruše­ní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é. Jest­liže uži­vatel komponenty požaduje přístup k vlastněným objektům, musí deklarovat vlastnosti, které po­skytují 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žadova­ného obrazu naší komponenty. Metoda Paint pro komponentu Shape musí provést několik věcí: použít pero a štětec vybraný uživate­lem, 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 kompo­nenty. 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 ne­chceme 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 kompo­nenty, 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. Mu­sí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ápo­vě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é vlast­nosti 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 kompo­nenty. 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 nespe­cifikujeme 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 kompo­nenty. 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 kom­ponenty, 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 ob­vykle 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é in­formace o naší komponentě, jestliže vytvoříme příslušné soubory nápovědy. Můžeme poskytnout malý sou­bor nápovědy s informacemi o našich komponentách a uživatelé mohou nalézt naši dokumentaci bez nut­nosti 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žo­vat tyto konvence: Každá komponenta musí mít obrazovku (obsahuje stručný popis a seznam všech vlast­ností, událostí a metod dostupných uživateli; obrazovka komponenty musí být označena poznámkou pod ča­rou 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 bi­ná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ři­pojen k přeložené aplikaci. Když uživatel spustí aplikaci, je přečten tento po­pis. Popis formuláře obsa­huje seznam vlastností formuláře společně s podrobným popisem všech komponent umístěných na formu­lá­ři. Každá komponenta, včetně samotného formuláře, je odpovědná za uložení a za­vádění svého vlastního po­pisu. Implicitně při samotném ukládání, komponenta zapíše hodnoty všech svých veřejných a zveřejňova­ných vlastností, které se liší od svých implicitních hodnot a to v pořadí jejich dekla­rací. Při zavádění, se kom­ponenta nejdříve vytvoří sama, pak nastaví všechny vlastnosti na jejich implicitní hodnoty a nakonec čte ulože­né neimplicitní hodnoty vlastností. Tento implicitní mechanismus slouží mnoha kom­ponentám a nevyžaduje žádnou akci od tvůrce komponent. Nicméně je několik způsobů, jak můžeme při­způsobit pro­ces ukládání a za­vá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 implicit­ní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í nu­lové 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 im­pli­citní hodnoty pro vlastnost, přidáme direktivu default a novou implicitní hodnotu na konec deklarace vlast­nosti. 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 au­tomaticky 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. Impli­citně všechny vlastnosti ve zve­řejňo­vané části deklarace objektu jsou ukládány. Můžeme zvolit jejich neu­kládání nebo navrhnout funkci, která při běhu programu určí zda vlastnost ukládat. K řízení ukládání vlast­nosti, 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 kompo­nentu, 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 nazva­ná Loaded, která provádí požadované změny v inicializaci. Volání Loaded proběhne před zobrazením formu­lá­ř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ý jed­noměsíční kalendář na základě kompo­nenty 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 kompo­nenty nazveme CalSamp, odvodíme nový typ komponenty nazvaný TSampleCalendar od TCustomGrid a regis­trujeme 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 ka­lendář 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 ka­len­dář nepotřebu­jeme. 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 kalen­dář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 vlast­ností tak, aby týden měl vždy sedm dní. Ke změně počátečních hodnot vlastností komponenty, předefinu­jeme konstruktor a nastavíme požado­vané 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. Prav­děpodobně budeme chtít změnit velikost ovladače a udělat všechny buňky viditelné. Dále si ukážeme jak re­agovat 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 kom­ponenta 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 nazva­nou 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 pro­gramu 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á repre­zentace 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 apli­kace, 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 pro­gramu, které poskytují přístup k tomuto datu. Přidání interního data ke kalendáři vyžaduje tři kroky: V prvním de­klarujeme 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řebu­jeme 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 po­skytnout 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 ka­lendář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ři­dá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 aktuali­zač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čas­ným datem. Implicitně výběr začíná v levé horní buňce, a tak je potřeba nastavit vlastnosti RowCol, když vytváříme kalendář a když změníme datum. K nastavení výběru na tento den, změníme metodu Update­Calendar 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 ma­nipu­lací, které často ovlivňují více než jednu vlastnost, a je tedy užitečné pro ně vytvořit metodu. Příkladem ta­kové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í kurzo­rové 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 klik­nutí, ale jestliže chceme změnit vybraný den, mu­sí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. Kalen­dář 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 specifiko­vaná 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, se­znamy, 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 edito­vat 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 vlast­nost 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 de­klaraci 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 kurzo­rových kláves. Kalendář pouze pro čtení používá metodu SelectCell pro všechny typy změn, včetně nastavování vlastností RowCol. 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 pro­vedené 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 po­skytuje 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 da­tové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řístu­pové 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 hla­vičkového souboru naší jed­notky. 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 vlast­nost 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. De­klarujeme vlastnosti DataSource a DataField a jejich implementační metody a zapíšeme metody jako “pře­dá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 sa­motným zrušením. Předefinujeme tedy konstruktor a destruktor kalendáře k vytváření a rušení objektu dato­vé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é po­lož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 ob­jekty datových spojů mají události nazvané OnDataChange. Když datový zdroj indikuje změnu ve svých da­tech, 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 dato­vé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 kompli­kovaně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 zpraco­vat zprávu, kterou Builder nezpracovává nebo že vytvoříme svou vlastní zprávu a budeme ji potřebo­vat zpraco­vat. Všechny objekty Builderu mají mechanismus pro zpracování zpráv. Základní myšlenkou zpra­cová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 speci­fiko­vaných me­tod závisejících na přijaté zprávě. Neexistuje-li metoda pro jistou zprávu, je použita impli­citní 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 Win­dows (včetně uživatelem definovaných zpráv) určené jistému objektu na volání metod. Tento mechanismus nebu­deme nikdy potřebovat měnit. Budeme potřebovat pouze vytvářet metody zpracování zpráv. Zpráva Win­dows 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 para­metrů a položce výsledku. Jeden parametr je 16 bitový a druhý 32 bitový. Jak často vidíme v kódu Windows, odka­zujeme se na tyto hodnoty jako na wParam (word parametr) a lParam (long parametr). Často každý z těchto parametrů obsa­huje 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 infor­mativní než wParam.

Builder zjednodušuje systém zpracování zpráv v několika směrech: Každá komponenta dědí kompletní sys­té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 bez­pečně zasílat kdykoli libovolnou zprávu libovolné komponentě. Jestliže komponenta nemá pro zprávu defi­novanou obsluhu, pak je použita im­plicitní obsluha, což obvykle ignoruje zprávu. Builder regis­truje me­todu 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 apli­kace. MainWndProc je statická metoda, která neobsahuje speciální zpracování některých zpráv. Přizpůso­bení provádíme až v WndProc, neboť každý typ komponenty může předefinovat tuto metodu podle svých po­tř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áves­nice 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 kompo­nenta 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ře­kládá mnoho zpráv Windows na události, které jak tvůrce komponenty, tak i uživatel komponenty může ob­sloužit. Lépe než měnit chování zpracování zpráv je měnit chování zpracování událostí. Ke změně zpraco­vá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 ob­sluhy zprávy. K předefinování metody zpracování zpráv, de­klarujeme novou metodu v chránění části naši komponenty a to se stejným jménem jako má metoda kterou předefino­vá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ě de­klarujeme 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 pa­rame­trů. Č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 situa­cí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řede­finová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ředefi­nová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 filtro­vány, když uživatel provádí tažení. Předefinování WndProc tomu pomáhá dvěma způsoby: Můžeme filtro­vat 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 bu­deme 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ázna­mu zprávy. Identifikátor zprávy je celočíselná konstanta. Windows rezervuje zprávy do 1024 pro svoje vlastní po­užití a tak když de­klarujeme svou vlastní zprávu musíme začít nad touto úrovní. Konstanta WM_USER repre­zentuje 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živatel­ský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 uka­zuje 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 nepo­u­ží­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ža­duje 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: Dekla­rujeme metodu v chráněné části deklarace třídy komponenty. Ujistíme se, že metoda vrací void. Nazveme me­todu po zpracovávané zprávě, ale bez znaků podtržení. Předáváme jeden parametr volaný odkazem na­zvaný 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 kon­struktoru 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 kom­po­nenta 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ři­způsobitelná. Dále si ukážeme jak vytvořit obalovou komponentu okolo generického formuláře About ob­saženého v Galerii Builderu. Nejprve zkopírujeme soubory ABOUT.H, ABOUT.CPP a ABOUT.DFM do našeho pracov­ní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 nasta­vovat počáteční stav dialogového okna, jako je titulek a počáteční nastavení ovladačů, a po uzavření dialogo­vé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 kompo­nenty nazveme AboutDlg, od TComponent odvodíme nový typ komponenty TAboutBoxDlg a registrujeme vy­tvář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é kom­ponentě jsou jednodušší než vlastnosti v normální komponentě. Umožňují pouze předávání dat mezi obalo­vou 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 apli­kace. 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á spe­cifi­kuje jméno vlast­nosti 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 kon­vencí má objektová položka pro ulo­žení hodnoty vlastnosti stejné jméno jako vlastnost, ale na začátku je při­dáno písmeno F. Např. k deklaraci vlastnosti rozhraní ce­ločí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, in­for­maci 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í dia­logo­vé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é vlast­nosti di­alogového okna na základě vlastností rozhraní obalové kom­ponenty. Po návratu z ShowModal, obal prav­dě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 for­muláře dialogového okna About. Jelikož okno About nevrací žádné informace, není nutno provádět nic po vo­lá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ásle­dují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ý sou­bor, 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 obsa­huje hlavičku knihovny komponent (okolo 100K). Můžeme minimalizovat tuto hlavičku vlože­ní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 pro­jektový 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í dialo­gové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 dialo­gové 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



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1248
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved