Umíme to s Delphi, 46. díl – vytvořte si vlastní komponentu, 3. část

V předchozích dvou dílech jsme se věnovali základům vytváření komponent. Posledně jsme skončili u vytváření vlastností jednoduchých datových typů. Dnes pokračujeme: nejprve se ještě vrátíme k vlastnostem a vysvětlíme si, jak vytvořit vlastnosti složitějších typů, pak se podíváme na metody a události komponent.
Vytváření vlastností typu objekt

Před týdnem jsme si vysvětlili, jak vytvářet vlastnosti jednoduchých typů, dále typu množina a výčtového typu. Často si ovšem nevystačíme s těmito „obyčejnými“ vlastnostmi jednoduchých typů, množin či výčtových typů. Potřebujeme, abychom mohli za „vlastnost“ prohlásit nějaký další, složitější datový typ, velmi často objektový. Objektem máme na mysli instanci třídy a potřebujeme tuto instanci používat jako vlastnost nějaké komponenty. Příkladem těchto vlastností jsou např. Font či HorzScrollBar.

Objektovou vlastnost poznáte opět podle znaménka plus (+) vedle názvu vlastnosti, a nebo podle znaménka … (tři tečky) v pravé části pole s hodnotou. Po klepnutí na znaménko + se objeví všechny publikované vlastnosti dané třídy, klepnutím na tlačítko tří teček (...) se zobrazí editor vlastnosti. Vlastnost typu objekt musí být (byť nepřímo) odvozena od typu TPersistent, aby fungovalo čtení hodnot do Object Inspectoru. Důležité je, že v Object Inspectoru se zobrazí pouze publikované vlastnosti dané třídy; ty ostatní (byť by byly veřejné) nikoliv.

Abychom mohli vlastnost typu objekt použít, musíme nejprve vytvořit příslušnou třídu, viz příklad:

type
  TObjektovyTyp = class(TPersistent)
    private
      FPodvlastnost1: Integer;
      FPodvlastnost2: Integer;
      FPodvlastnost3: String;
    published
      property Podvlastnost1: Integer read FPodvlastnost1 write FPodvlastnost1;
      property Podvlastnost2: Integer read FPodvlastnost2 write FPodvlastnost2;
      property Podvlastnost3: String read FPodvlastnost3 write FPodvlastnost3;
  end;

Nyní je třída vlastnosti hotová, nicméně její použití není tak jednoduché jako v případě předchozích typů vlastností. Máme vytvořenu třídu TObjektovyTyp. Abychom ale její objekt mohli použít jako vlastnost jiné třídy (komponenty), musíme jej nejprve vytvořit. Jinými slovy: nejprve je zapotřebí vytvořit instanci třídy TObjektovyTyp, tedy objekt třídy TObjektovyTyp. Tento objekt musí být vytvořen již v okamžiku vytváření komponenty. Nezbývá nám tedy než předefinovat konstruktor třídy TUkazkovaKomponenta a v tomto konstruktoru vytvořit instanci třídy TObjektovyTyp. Důležité je, že v konstruktoru musíme nejprve zavolat konstruktor předka (to je třeba učinit takřka vždy a je třeba si to pamatovat). Za předpokladu, že vlastnost typu objekt se ve třídě TUkazkovaKomponenta bude jmenovat ObjektovaVlastnost, bude konstruktor vypadat takto:

constructor TUkazkovaKomponenta.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FObjektovaVlastnost := TObjektovyTyp.Create;
end;    // constructor Create

Nyní máme objekt vytvořen, ovšem musíme jej po sobě (po skončení práce) také uvolnit. To provedeme v rámci destruktoru Destroy, který také předefinujeme. Na konci předefinovaného destruktoru musíme opět zavolat destruktor předka:

destructor TUkazkovaKomponenta.Destroy;
begin
  FObjektovaVlastnost.Free;
  inherited Destroy;
end;    // destructor Destroy

Tím jsou všechny potřebné kroky hotovy. Pro jistotu si uvedeme kompletní zdrojový kód komponenty:

unit UkazkovaKomponenta;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TObjektovyTyp = class(TPersistent)
    private
      FPodvlastnost1: Integer;
      FPodvlastnost2: Integer;
      FPodvlastnost3: String;
    published
      property Podvlastnost1: Integer read FPodvlastnost1 write FPodvlastnost1;
      property Podvlastnost2: Integer read FPodvlastnost2 write FPodvlastnost2;
      property Podvlastnost3: String read FPodvlastnost3 write FPodvlastnost3;
  end;

  TUkazkovaKomponenta = class(TCustomControl)
  private
    { Private declarations }
    FObjektovaVlastnost: TObjektovyTyp;
  protected
    { Protected declarations }
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    { Public declarations }
  published
    property ObjektovaVlastnost: TObjektovyTyp read FObjektovaVlastnost write FObjektovaVlastnost;
    { Published declarations }
  end;

procedure Register;

implementation

constructor TUkazkovaKomponenta.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FObjektovaVlastnost := TObjektovyTyp.Create;
end;    // constructor Create

destructor TUkazkovaKomponenta.Destroy;
begin
  FObjektovaVlastnost.Free;
  inherited Destroy;
end;    // destructor Destroy

procedure Register;
begin
  RegisterComponents(`Nase`, [TUkazkovaKomponenta]);
end;

end.

Object Inspector nyní vypadá takto:

Vytváření vlastností typu pole

Dalším netriviálním typem vlastnosti je typ pole. S vlastnostmi typu pole pracujeme obdobně jako s pascalskými proměnnými typu pole, pouze s jednou hlavní odlišností. Zatímco indexy polí v objektovém Pascalu mohou být pouze celočíselné (nebo jiné ordinální), vlastnosti typu pole mohou být indexovány i podle jiných typů (např. podle řetězců).

Vlastnosti typu pole poznáme v Object Inspectoru podle tlačítka se třemi tečkami v pravé části políčka pro hodnotu vlastnosti. Klepnutím na toto tlačítko se otevře specializovaný editor vlastnosti. Tím se dostáváme k relativně velkému problému spojenému s vlastnostmi typu pole: aby byly přístupné (a modifikovatelné) již v době návrhu, musíme k nim vytvořit editor vlastnosti. Nestačí tedy (jako u předchozích typů), že jsou publikované. Protože k editorům vlastností se v našem seriálu asi bohužel nedostaneme, proto v případě polí zůstaneme u těchto informací. Pro podrobnější informace o polích vás odkazuji na nápovědu k Delphi, kde jsou pole zpracovány velmi důkladně.

Součásti deklarace vlastnosti

V minulém dílu seriálu jsme si ukázali a stručně popsali syntaxi deklarace vlastnosti (property). Dnes se k ní ještě vrátíme a její jednotlivé fragmenty popíšeme podrobněji. V zápisu je možné používat následující direktivy: Default, NoDefault, Stored, Index a Dispid.

Direktiva Default

Jak jsme poznali, nově vytvářené vlastnosti mají implicitně po svém vytvoření hodnotu nula (příp. jinou „nulovou“ hodnotu, např. prázdný řetězec). Pokud chceme, aby default hodnota vlastnosti byla jiná, specifikujeme ji direktivou default. Příklad:

property Cislo: Integer read FCislo write FCislo default 5;
property VyctovyTyp: TVyctovyTyp read FVyctovyTyp write FVyctovyTyp default vtHodnota1;

Musíme si ovšem vysvětlit, k čemu direktiva default opravdu slouží, protože zpravidla vyvolává představu poněkud jiné funkčnosti, než opravdu má. Představte si, že jste právě v prostředí Delphi založili novou aplikaci. Upravíte formulář, umístíte některé komponenty. V okamžiku, kdy se rozhodnete uložit zdrojové kódy, Delphi ukládají (kromě jiného) také soubor popisující formulář <.DFM). V tomto okamžiku Delphi porovnají stávající hodnoty vlastností s jejich default hodnotami a pokud se liší, uloží hodnotu dané vlastnosti do souboru *.DFM. Pokud nikoliv, neukládá se nic a při příštím načtení se nastaví tato implicitní hodnota. A pouze k tomu slouží direktiva Default! Nečekejte proto od ní, že její uvedení způsobí prvotní nastavení hodnoty vlastnosti v Object Inspectoru. To musíte udělat sami, ručně, typicky v konstruktoru.

Důležitá poznámka: poněkud jiný význam má direktiva Default pro vlastnosti typu pole. Podrobný popis však přesahuje rámec našeho seriálu.

Direktiva NoDefault

Tato direktiva slouží ke specifikaci, že „příslušná vlastnost nemá žádnou default hodnotu“. Ptáte se, nač je to vhodné? Nestačilo by prostě žádnou default hodnotu (tedy direktivu Default) neuvádět? Nestačilo! :-) Přesněji řečeno – direktiva NoDefault se dostává ke slovu v případě zděděných vlastností. Pokud vytvoříte komponentu od třídy TClass1, která definuje default hodnotu pro vlastnost Property1, můžete následující řádkou zajistit, že tato zděděná implicitní hodnota bude potlačena. Na zděděnou vlastnost Property1 tedy bude nadále pohlíženo jako na vlastnost bez implicitní hodnoty:

property Property1 NoDefault;

Direktiva Stored

Tato direktiva opět souvisí s ukládáním hodnoty vlastnosti do souboru *.DFM (a stejně jako direktivy Default a NoDefault tedy nemá žádný vliv na chování výsledného programu). Za direktivou Stored musí následovat hodnota True nebo False (případně funkce, která některou z těchto hodnot vrátí). Direktiva Stored True (případně neuvedení direktivy Stored) samozřejmě způsobí uložení hodnoty, direktiva Stored False způsobí opak.

Možná teď vyvstává otázka, jak spolu interagují direktivy Stored a Default. Platí tedy zlaté pravidlo: hodnota vlastnosti je uložena pouze, pokud je tato hodnota zároveň rozdílná od implicitní hodnoty (nebo pokud není uvedena žádná implicitní hodnota) a direktiva Stored je nastavena na True (případně Stored není vůbec uvedena). V ostatních případech se hodnota neukládá.

Direktivy Index a Dispid

Použití těchto direktiv je již tak trochu „vyšší dívčí“, a proto mi opět nezbývá než případné zájemce z prostorových důvodů odkázat na nápovědu či dokumentaci k Delphi. Nutno ovšem podotknout, že problematiku tvorby komponent rozebíráme opravdu podrobně, proto nemusíte mít obavy, že direktivy Index a Dispid patří mezi její základní prvky, a jejich neznalost vám znemožní smysluplnou práci.

Čtení a zápis hodnot vlastností

Až dosud jsme se vůbec nevěnovali možnostem, jak číst a zapisovat hodnotu vlastností. A přitom právě to je velkou silou vlastností vůbec. Ve všech předchozích případech jsme četli a zapisovali hodnotu takto:

property Cislo: Integer read FCislo write FCislo;

Tento způsob je zcela přímočarý: při čtení hodnoty vlastnosti Cislo se přečte hodnota atributu FCislo, při zápisu hodnoty do vlastnosti Cislo se změní hodnota atributu FCislo. Nic více, nic méně. Představte si však následující situaci (přičemž není nutné aplikovat ji výhradně na komponentu!): vaše komponenta obsahuje nějakou vlastnost. Vy potřebujete, aby se při změně hodnoty této vlastnosti provedlo několik akcí, například nastavení nějakého titulku, aktualizace nějakého seznamu, posunutí nějakého ukazatele průběhu a přidání nějakého prvku do pole. Jak toho docílit? První odpověď asi bude znít – snadno! Prostě ve zdrojovém kódu za přiřazení hodnoty do této vlastnosti zapíšeme příslušné příkazy, které modifikují a aktualizují zmíněné další komponenty a proměnné.

Možná teď není úplně jasné, co mám na mysli. Předvedu vše proto raději na příkladu. Následující zdrojový kód ošetřuje událost OnClick tlačítka btnPrirad. V obsluze této události přiřadíme hodnotu 1 do vlastnosti Pozice komponenty NaseKomponenta a zároveň provádíme několik dalších, doprovodných, souvisejících akcí. Po praktickém využití této procedury raději nepátrejte, spokojte se s pochopením smyslu:

procedure TfrmHlavni.btnPriradClick(Sender: TObject);
begin
  NaseKomponenta.Pozice := 1;
  frmHlavni.Caption := `Pozice` + NaseKomponenta.Pozice;
  ListBox.Clear;
  ListBox.Items.Add(`Pozice` + NaseKomponenta.Pozice + `.`);
  ScrollBar.Position := NaseKomponenta.Pozice;
end;

Ano, tento způsob se velmi často používá, nicméně není úplně hifi. Co když se hodnota této vlastnosti může měnit (kromě této procedury) ještě v hlavní nabídce, pomocí kontextové nabídky nebo jen tak, v průběhu výpočtu nějaké funkce? To budeme za každé přiřazení hodnoty opisovat vždy znovu a znovu čtyři řádky kódu navíc?

Ne, nic takového dělat nebudeme:-) Možným řešením by bylo vytvořit proceduru, která tyto kroky učiní a volat ji vždy, když se hodnota mění. My ale využijeme ještě lepšího řešení: pro vlastnost Pozice komponenty NaseKomponenta vytvoříme tzv. přístupové metody. Tyto metody se budou (automaticky) volat vždy, když se hodnota vlastnosti bude číst nebo zapisovat.

Přístupové metody jsou funkce a procedury, které umožňují čtení a zápis hodnot vlastností. Názvy funkcí sloužící ke čtení podle konvence začínají slůvkem Get, názvy funkcí pro zápis slůvkem Set.Čtecí funkce nemají žádné parametry a vrací hodnotu stejného typu jako je vlastnost. Zápisové procedury nevrací nic a mají jeden parametr stejného typu, jako je vlastnost. Přístupové metody se obvykle deklarují v soukromé sekci třídy. Čtecí metoda se automaticky volá vždy, když někam přiřazujeme (nebo jakkoliv jinak čteme) hodnotu vlastnosti. Zápisová metoda je vyvolána při každém přiřazení hodnoty do vlastnosti. Tyto metody se nepoužívají jen z důvodu případných dalších akcí, které v souvislosti se čtením a zápisem chceme provádět. Pomocí nich se také realizuje autorizovaný přístup k datům, neboť ne vždy je vhodné dát uživatelům komponent možnost přímo zapisovat do privátních atributů.

Pojďme se podívat na příklad komponenty, která přistupuje ke své vlastnosti pomocí přístupových metod. Při zapisování hodnoty ji pro ilustraci také přiřadí do svého editačního pole.

unit UkazkovaKomponenta;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TUkazkovaKomponenta = class(TCustomEdit)
  private
    { Private declarations }
    FHodnota: Integer;
    procedure SetHodnota(Hodn: Integer);
    function GetHodnota: Integer;
  protected
    { Protected declarations }
  public
    { Public declarations }
  published
    property Hodnota: Integer read GetHodnota write SetHodnota;   
    { Published declarations }
  end;

procedure Register;

implementation

procedure TUkazkovaKomponenta.SetHodnota(Hodn: Integer);
begin
  FHodnota := Hodn;
  Caption := IntToStr(FHodnota);
end;    // procedure SetHodnota

function TUkazkovaKomponenta.GetHodnota: Integer;
begin
  Result := FHodnota;
end;    // procedure GetHodnota

procedure Register;
begin
  RegisterComponents(`Nase`, [TUkazkovaKomponenta]);
end;

end.

Všimněte si, že použití direktivy Default vůbec nezpůsobí nastavení implicitní hodnoty vlastnosti! K tomu je nutné předefinovat konstruktor (nezapomeňte v něm však nejprve vyvolat konstruktor předka):

unit UkazkovaKomponenta;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TUkazkovaKomponenta = class(TCustomEdit)
  private
    { Private declarations }
    FHodnota: Integer;
    procedure SetHodnota(Hodn: Integer);
    function GetHodnota: Integer;
  protected
    { Protected declarations }
  public
    constructor Create(AOwner: TComponent); override;
    { Public declarations }
  published
    property Hodnota: Integer read GetHodnota write SetHodnota default 5;
    { Published declarations }
  end;

procedure Register;

implementation

procedure TUkazkovaKomponenta.SetHodnota(Hodn: Integer);
begin
  FHodnota := Hodn;
  Caption := IntToStr(FHodnota);
end;    // procedure SetHodnota

function TUkazkovaKomponenta.GetHodnota: Integer;
begin
  Result := FHodnota;
end;    // procedure SetHodnota

constructor TUkazkovaKomponenta.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Hodnota := 5;
end;    // constructor Create

procedure Register;
begin
  RegisterComponents(`Nase`, [TUkazkovaKomponenta]);
end;

end.

Na tomto místě již je asi vhodné podotknout, že není nutné používat vlastnosti a přístupové metody pouze při vytváření komponent! Nic nám nebrání použít vlastnost v libovolné třídě, přistupovat k ní pomocí přístupových metod a využívat všech výhod, které jsme u jejich popisu nastínili: tedy provádět v jejich rámci jednotně případné další akce a inicializace. Vlastnosti samozřejmě nemusíte uvádět vždy v sekci published, můžete si vytvořit klidně svou soukromou (private) nebo veřejnou (public) vlastnost, která nebude publikována a bude sloužit jen ke komfortnímu provádění zmíněných akcí. Je dobré si uvědomit, že i když byste stejného výsledku mohli dosáhnout deklarací privátního atributu a vlastních „přistupovacích“ metod, použití properties je velmi elegantnější – především z toho důvodu, že přístupové metody vlastností jsou volány automaticky při každé operaci s vlastností; nemusíte je tedy vyvolávat explicitně.

Inicializace hodnot

Velmi často potřebujeme, aby vlastnost měla hned při umístění komponenty na formulář nějakou (implicitní) hodnotu. V předchozích odstavcích jsme poznali, že nestačí použít direktivy Default. První možnost jsme poznali v rámci příkladu v 44. díle seriálu: implementovat funkci CreateWnd. V zásadě existují dvě další možnosti, jak (kdy) hodnotu vlastnosti inicializovat:
  • Předefinovat konstruktor Create;
  • Předefinovat metodu Loaded.
Předefinování konstruktoru Create

Tuto metodu jsme v předchozích odstavcích již také několikráte použili. V předefinovaném konstruktoru Create je možné inicializovat interní (a samozřejmě i soukromá) datová pole. Je tedy možné přiřadit jim „implicitní“ hodnotu. Při použití přiřazení v konstruktoru se také velmi často vytvářejí instance případných vlastností typu objekt. Nezapomeňte, že vše, co se v konstruktoru Create ručně vytvoří (tedy např. instance tříd) se musí v destruktoru Destroy ručně uvolnit, proto velmi často zároveň s konstruktorem přetěžujeme i destruktor. Z tohoto tvrzení existují výjimky: představte si například, že jednou z vlastností vytvářené komponenty je jiná komponenta (třeba objekt třídy TTimer – časovač). V konstruktoru tento objekt vytvoříme, ale při jeho vytváření uvedeme svou komponentu jako vlastníka časovače. Při uvolnění komponenty z paměti tedy dojde automaticky k uvolnění všech objektů, které tato komponenta vlastní – časovač se tedy uvolní automaticky a vůbec nemusíme předefinovávat destruktor.

Závěrem tohoto odstavce si zopakujeme velmi důležité pravidlo, které je nutné dodržovat při práci s předefinovaným konstruktorem: v drtivé většině případů je zapotřebí v těle předefinovaného konstruktoru nejprve zavolat konstruktor předka a teprve po jeho dokončení provést vlastní inicializace (podobné pravidlo platí ostatně pro předefinovaný destruktor: provedeme „své“ akce a na závěr předefinovaného destruktoru vyvoláme destruktor předka). Pokud se divíte, proč není třeba nutné konstruktor a destruktor předka vyvolávat vždy, vězte, že pokud tyto speciální metody nepředefinujeme, používají se přímo verze implementované v předkovi – a vyvolávají se implicitně.

Předefinování metody Loaded

V předefinované metodě Loaded je možné inicializovat interní datová pole komponenty po jejím načtení z uloženého popisu (ze souboru *.DFM), ale ještě před jejím zobrazením na formuláři (přesněji řečeno – ještě před zobrazením formuláře). Ihned po načtení je vyvolána virtuální metoda Loaded. Nemusíte se tedy obávat žádného kmitání či překreslování obrazu v závislosti na provedených inicializacích. Chcete-li v těle metody Loaded provádět inicializace hodnot vlastností (či cokoliv jiného), musíte ji předefinovat.

Pozor: stejně jako v případě konstruktoru platí i u metody Loaded, že v jejím úplném úvodu musíte volat metodu Loaded předka, aby se mohly správně inicializovat zděděné vlastnosti ještě předtím, než začnete inicializovat vámi definované vlastnosti.

Možná si teď říkáte, proč používat metodu Loaded, když je možné provést inicializace v konstruktoru. Možná vše lépe osvětlí následující sekvence akcí, které se provádějí při čtení komponenty ze souboru *.DFM:

  • Vytvoří se instance komponenty
  • Interní datová pole se inicializují na nulu, případně na jinou hodnotu určenou v předefinovaném konstruktoru Create. Je tedy proveden předefinovaný konstruktor.
  • Ze souboru *.DFM se načtou data uložená s komponentou.
  • Zavolá se metoda Loaded.
  • Komponenta se zobrazí na formuláři.
Zde je tedy dobře patrné, proč jsme ve 44. dílu seriálu nemohli nastavovat vlastnost Items již v konstruktoru Create. Nemělo by to smysl, protože vlastnost Items se až po volání konstruktoru Create načítá ze souboru *.DFM. V onom ukázkovém příkladu jsme ovšem inicializace prováděli v metodě CreateWnd. Jaký to má význam? Proč jsme nepoužili metodu Loaded?

Problematika inicializace hodnot je nesmírně zajímavá a pouhou volbou vhodné metody je možné modifikovat chování komponenty jak za běhu programu, tak i v době návrhu. Abychom si vše náležitě osvětlili, vytvoříme následující příklad: vytvořte novou komponentu odděděnou třeba od TComboBox (postup je uveden ve 44. díle seriálu, začněte Component – New Component...) a předefinujte její metody Create, CreateWnd a Loaded. Zdrojový kód je následující:

unit Tiskarny;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Printers;

type
  TTiskarny = class(TComboBox)
  private
    { Private declarations }
  protected
    { Protected declarations }
  public
    procedure CreateWnd; override;
    constructor Create(AOwner: TComponent); override;
    procedure Loaded; override;
    { Public declarations }
  published
    { Published declarations }
  end;

procedure Register;

implementation

constructor TTiskarny.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ShowMessage(`Konstruktor Create`);
end;    // constructor Create


procedure TTiskarny.Loaded;
begin
  inherited Loaded;
  ShowMessage(`Metoda Loaded`);
end;    // procedure CreateWnd


procedure TTiskarny.CreateWnd;
begin
  inherited CreateWnd;
  ShowMessage(`Metoda CreateWnd`);
end;    // procedure CreateWnd

procedure Register;
begin
  RegisterComponents(`Nase`, [TTiskarny]);
end;

end.

Všechny metody prozatím dělají totéž –pouze vyvolají tutéž metodu předka a vypíší informační okno říkající, že je tato metoda zavolána. Když nyní tuto komponentu instalujete a použijete v nějaké aplikaci (postup opět viz 44. díl seriálu), bude vše fungovat podle předpokladů. Představte si, že chceme inicializovat hodnotu vlastnosti Left této komponenty. Budeme tedy postupně přidávat do jednotlivých předefinovaných metod následující řádku:

  Left := 250;

Budeme pozorovat, jaký význam bude mít její přidání do konstruktoru Create, do metody CreateWnd a do metody Loaded.

Po přidání do konstruktoru Create bude tělo tohoto konstruktoru vypadat takto:

constructor TTiskarny.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Left := 250;
  ShowMessage(`Konstruktor Create`);
end;    // constructor Create

Zkusíte-li si nyní komponentu přeložit a použít v aplikaci, zjistíte, že nastavení hodnoty Left na 250 nemá vůbec žádný význam – ani v době návrhu, ani za běhu programu. Nyní zmíněnou řádku z konstruktoru zase odeberte a přidejte ji do metody Loaded:

procedure TTiskarny.Loaded;
begin
  inherited Loaded;
  Left := 250;
  ShowMessage(`Metoda Loaded`);
end;    // procedure CreateWnd

Opět komponentu přeložte a vyzkoušejte v nové aplikaci. Zjistíte, že při prvotním vložení komponenty na formulář (v době návrhu) nemá nastavení této hodnoty žádný význam: komponenta se umístí na místo, kam klepnete myší. Když ale nyní zkusíte aplikaci uložit, a pak znovu načíst, zjistíte, že najednou se komponenta zobrazila se „správným“ nastavením hodnoty Left. Důvodem je, že při prvotním umístění komponenty na formulář není volána metoda Loaded, zatímco při každém dalším načítání aplikaci do prostředí Delphi již ano. A jelikož je metoda Loaded volána i při spouštění aplikace, bude za běhu hodnota Left nastavena na 250.

Nyní učiníme poslední část pokusu: vymažte řádku nastavující hodnotu Left z metody Loaded a přidejte ji do metody CreateWnd:

procedure TTiskarny.CreateWnd;
begin
  inherited CreateWnd;
  Left := 250;
  ShowMessage(`Metoda CreateWnd`);
end;    // procedure CreateWnd

Použijete-li nyní tuto komponentu v aplikaci, bude hodnota Left „správná“ jak při prvotním umístění na formulář, tak i při dalších načítání, stejně jako za běhu programu.

Řekněte sami, není Delphi krásné? :-)

Na závěr

Popis tvorby komponent se nám úspěšně protahuje. Zatím ale nemám pocit, že bych uvedl něco naprosto zbytečného, něco, co nemá sebemenší význam. Pokud ovšem máte opačný názor, budu velmi rád, pokud mi jej bez obav sdělíte.

V každém případě problematiku tvorby komponent za týden (konečně) dokončíme: začneme rovnou povídáním o definicích metod a dostaneme se také k událostem. Až budeme vše umět, probereme způsoby testování komponenty a na závěr si společně jednu komponentu vytvoříme. Těším se nashledanou.

Váš názor Další článek: Studenti v Americe se maj

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