Živě.cz o počítačích a internetu

Tipy a triky v Delphi, díl 155. - tvorba vlastních komponent

Jan Šindelář 22.9.2004

Dnešním dílem začneme jedno z dalších velkých témat, kterému jsme se zatím vyhýbali. Budeme si povídat o tvorbě vlastních komponent.

Jelikož je to téma velmi obsáhlé, není možné vše napsat jednoduše do jednoho krátkého článku. Proto se budeme k tomuto tématu vracet nepravidelně i v budoucích dílech. Pro dnešek si vytvoříme velmi jednoduchou vizuální komponentu. Berte proto tento díl spíše jako nástin budoucích pokusů a základní informace pro přehled nejen pro začátečníky.

Vynecháme nějakou detailní teorii, zájemci si jistě najdou na internetu řadu materiálů a náš seriál přeci jen šel vždy více do praxe. Nám bude zatím stačit to, že komponenty jsou objekty, tudíž pro ně platí všechny zásady a výhody objektově orientovaného programování (jako je třeba dědičnost). Používají se pochopitelně proto, abychom si usnadnili práci a znovu a znovu mohli ve svých aplikacích používat opakující se kód. Ideální je také to, že používáním komponent (a nemusí samozřejmě jít jen o ty vizuální) dojde k rozdělení kódu na relativně samostatné části, ve kterých se lépe opravují chyby a aplikace se může snadněji aktualizovat. Komponenty mají své vlastnosti (nastavitelné obvykle v Object Inspectoru), události a metody. Dost už ale teorie, tohle stejně již všichni vědí a používají u standardních komponent.

Vrhneme se rovnou na tvorbu naší první komponenty. Co bude umět? Bude to jednoduchá vizuální komponenta v podobě čáry. Čára bude mít nastavitelnou barvu, tloušťku a jeden ze čtyř možných směrů. Jak začneme? Nejrychlejší cesta je přes nabídku Component a první položku New Component.

Zobrazí se nám dialogové okno, kde musíme provést prvních několik zásadních rozhodnutí. První položka Ancestor type nám určuje nadřazený (rodičovský) objekt pro naši budoucí komponentu. Tím může být již nějaká jiná konkrétní komponenta (třeba tlačítko) a nebo i více obecnější objekt až na nejvyšší úroveň TComponent. My budeme kreslit čáru a proto si zvolíme objekt TGraphicControl. Položka ClassName pak určuje název třídy naší komponenty a pro náš příklad si zvolíme například název TLine. V další položce pak zvolíme, na jakou záložku palety komponent budeme chtít naši komponentu umístit. Můžeme nechat předvolenou variantu Samples. Chcete-li, můžete ještě určit složku, kam bude zdrojový kód komponenty uložen. Pak již jen stiskneme OK.

Delphi nám připraví kostru zdrojového kódu naší komponenty. Zatím vidíte, že je celkem prázdná. První věc bude, že do seznamu používaných jednotek uses přidáme jednotku Graphics (pokud tam již ovšem není, záleží na používané verzi Delphi). Tu budeme potřebovat kvůli barvě čáry, která bude reprezentována typem TColor.

Dále nadeklarujeme jeden vlastní nový typ, který bude určovat směr čáry.

TDir = (sHorizont, sVertical, sDiagon1, sDiagon2);

Názvy jednotlivých "směrů" jakož i samotný název typu je pochopitelně volitelný. Jednotlivé varianty pak představují horizontální, vertikální a oba diagonální směry čáry.

Teď je čas přidat zmiňované vlastnosti. Budeme je přidávat do sekce private. Nadeklarovaný máme již typ TDir, takže teď jen přidáme příslušnou proměnnou tohoto typu s názvem FLineDir. Pro barvu ani tloušťku čáry zvláštní proměnnou potřebovat nebudeme, protože hodnotu budeme získávat (a také zpětně zapisovat) přímo do vlastností plátna (canvas). Dále potřebujeme příslušné funkce (metody) pro načtení (Get) či naopak nastavení (Set) hodnot příslušných vlastností. Celá sekce private pak bude vypadat takto:

private
    { Private declarations }
    FLineDir: TDir;
    function GetLineWidth: Integer;
    function GetLineColour: TColor;
    procedure SetLineWidth(const NewWidth: Integer);
    procedure SetLineColour(const NewColour: TColor);
    procedure SetLineDir(const NewDir: TDir);

Nyní je potřeba napsat do části implementation samotné funkce procedury. V našem případě budou velmi jednoduché a kód myslím není skoro nutné ani komentovat.

implementation

function TLine.GetLineWidth: Integer;
begin
  Result := Canvas.Pen.Width;
end;

function TLine.GetLineColor: TColor;
begin
  Result := Canvas.Pen.Color;
end;

procedure TLine.SetLineWidth(const NewWidth: Integer);
begin
  if NewWidth <> Canvas.Pen.Width then
  begin
    Canvas.Pen.Width := NewWidth;
    Invalidate;
  end;
end;

procedure TLine.SetLineColor(const NewColor: TColor);
begin
  if NewColor <> Canvas.Pen.Color then
  begin
    Canvas.Pen.Color := NewColor;
    Invalidate;
  end;
end;

procedure TLine.SetLineDir(const NewDir: TDir);
begin
  if NewDir <> FLineDir then
  begin
    FLineDir := NewDir;
    Invalidate;
  end;
end;

Za zmínku stojí snad jen procedura Invalidate. Jak možná tušíte, slouží k překreslení komponenty, takže ji voláme vždy, pokud došlo k nějaké změně parametrů. Všimněte si, že k nastavení parametrů (resp. překreslení) dojde opravdu jen při změně hodnoty dané vlastnosti, takže pokud uživatel zvolí například stejnou barvu jako je ta aktuální, nebude se zbytečně nic překreslovat, protože k tomu není důvod.

Máme vlastnosti, máme implementován způsob jejich změny, ale ještě nemůžeme samotné vlastnosti měnit. Toho docílíme tím, že do části public tyto vlastnosti vypíšeme patřičným způsobem a to tak, že určíme název vlastnosti, typ a poté metody pro čtení a zápis (které už máme implementované v předchozím kroku). Zápis bude vypadat takto:

published
  { Published Declarations }
  property Direction: TDir read FLineDir write SetLineDir;
  property LineColor: TColor read GetLineColor write SetLineColor;
  property LineWidth: Integer read GetLineWidth write SetLineWidth;

Vše, co je zapsáno tímto způsobem v sekci published, pak bude vidět v Object Inspectoru a můžeme tak tyto vlastnosti snadno měnit.

Zbývá nám zařídit samotné kreslení čáry. Jedná se o grafický objekt, takže má již definovánu (zděděnu) proceduru Paint pro vykreslení. My jen tuto proceduru přepíšeme podle našich představ. Přidáme její deklaraci do části protected:

protected

{ Protected declarations }

procedure Paint; override;

A samotný kód poté do implementační části:

procedure TLine.Paint;
var
  start: Integer;
begin
  inherited;
  case FLineDir of
    sHorizont:
      begin
        start := (Height - Canvas.Pen.Width) div 2;
        Canvas.MoveTo(0, start);
        Canvas.LineTo(Width, Start);
      end;
    sVertical:
      begin
        start := (Width - Canvas.Pen.Width) div 2;
        Canvas.MoveTo(start, 0);
        Canvas.LineTo(start, Height);
      end;
    sDiagon1:
      begin
        Canvas.MoveTo(0, 0);
        Canvas.LineTo(Width, Height);
      end;
    sDiagon2:
      begin
        Canvas.MoveTo(Width, 0);
        Canvas.LineTo(0, Height);
      end;
  end;
end;

A tím máme vlastně hotovo. Vytvořenou komponentu pak obvyklým způsobem nainstalujeme do Delphi a můžete si ji hned zkusit použít v nějakém zkušebním projektu. Celý zdrojový kód můžete stahovat zde.

Je to ale všechno? Pro dnešní díl určitě a takto primitivní komponenta je vlastně hotova. O dalších komplikovanějších postupech, o konstruktorech a destruktorech a jiných užitečných tipech si povíme zase někdy jindy.

Zdrojem informací pro článek byl pan Alistair Keys