18. fejezet: Objektumok létrehozása

A feladat

Készítsünk egy programot, amely egy beadott egész szám minden számjegyéhez létrehoz egy gombot! Mivel nem tudjuk előre, hány számjegyünk lesz, nem gyárthatjuk le tervezési időben a gombokat. A gombok futási időben jönnek létre, ezért a tervezőt nem használhatjuk a gombok tulajdonságainak beállítására, ezt is futási időben kell megtenni. Legyen egy gomb mérete 32×32 pixel, a gombok közötti távolság 10 pixel.

Előkészítés

Kezdetben legyen a formon egy szövegdoboz (Edit1, ide írjuk be a számot), és egy indítógomb (Button1).


A gombra dupla kattintással hozd létre az eseménykezelő üres vázát, a kód többi részét majd ebbe fogjuk írni.

A gombok létrehozása

Az eseménykezelőben szükségünk lesz egy i változóra, amely végigmegy Edit1 karakterein. Ezenkívül deklarálnunk kell egy TButton típusú objektumot (valójában osztályt!) is, amely az éppen létrehozott gombra hivatkozik. Ezután minden karakterhez (melyek számjegyek) létrehozunk egy gombot:
procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
    g:TButton;
begin
 for i:=1 to length(Edit1.Caption) do begin
   g:=TButton.Create(Form1);
   g.Parent:=Form1;
   g.Height:=32;
   g.Width:=32;
   g.Top:=50;
   g.Left:=i*10+(i-1)*32;
   g.Caption:=Edit1.Caption[i];
   g.Font.Size:=18;
 end;
end;

Először létrehozzuk az adott gombot, majd beállítjuk a tulajdonságait.

Mint látható, a TButton konstruktorának van egy paramétere. Ezt onnan tudhatjuk, hogy a Create begépelésekor megvárjuk (vagy CTRL+szóköz lenyomásával előhívjuk) a szerkesztő súgóját, mely kiegészíti a begépelt azonosítót, és itt a listában láthatjuk a Create paraméterét. Mivel ennyi azonosítót nem tudunk megjegyezni, ez a súgó nagyon hasznos segédeszköz, főként, hogy az azonosítók nevéből azok feladata is sokszor kitalálható.
A konstruktor paramétere azt adja meg, hogy melyik másik objektum a tulajdonosa a gombnak. Ez azért fontos, mert a form bezárásakor a Lazarusnak nem csak a formot, hanem az általa birtokolt objektumokat is meg kell semmisíteni, és ezt a tulajdonosi adatok alapján végzi el.

A másik új tulajdonság a Parent: ez grafikus objektumoknál azt jelzi, hogy az adott objektum melyik másik objektum belsejében jelenik meg (továbbá a Top, Left értékek is a Parent bal felső sarkához képest értendők).

A Left értékét a gomb sorszámából, a gombok szélessége és a térközök alapján állapítottuk meg.

Eseménykezelők és típuskényszerítés

Bonyolítsuk a feladatot! Egy újonnan létrehozott gomb megnyomásakor a rajta lévő számjegy értéke nőjön 1-gyel (a 9-ből meg legyen 0). Ehhez a gombok eseménykezelőjét is be kell állítani, most már a szerkesztő segítsége nélkül.
Figyeld meg a Button1 eseménykezelőjét! Ennek mintájára el kell készítenünk egy saját eseménykezelőt, de az összes új gomb eseménykezelője lehet ugyanaz az eljárás (hiszen mindegyik gomb esetén ugyanazt csinálja).
Honnan fogja tudni az eseménykezelő, melyik gomb megnyomása váltotta ki az eseményt? Ezt a célt szolgálja a Sender paraméter, mely tartalmazza az eseményt kiváltó objektum címét.
Legegyszerűbb, ha az új eseménykezelő fejlécét a meglévő másolásával hozod létre.
Először a form osztályát egészítjük ki az új metódus fejlécével:
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure SajatClick(Sender:TObject);

Majd megírjuk az eljárást:
procedure TForm1.SajatClick(Sender:TObject);
var g:TButton;
begin
 g:=TButton(Sender);
 g.Caption:=IntToStr((StrToInt(g.Caption)+1) mod 10);
end;

Az egyszerűség kedvéért deklaráltunk egy g változót, mely az adott gombra mutat.

Logikusan g:=Sender lenne az utasítás, de ez fordításkor hibaüzenetet ad. Ennek az az oka, hogy a Sender típusa általános TObject, míg g típusa TButton. Ezért a típuskényszerítés (typecast) műveletével meg kell adnunk, hogy a Sender-ben tárolt értéket tekintse úgy, mint egy TButton típusú mutató. A típuskényszerítés általános formája:

típus(kifejezés)

Ekkor a Pascal a kifejezés eredeti típusától függetlenül azt a megadott típusúnak tekinti. Ez többnyire csak akkor működhet, hogyha a kifejezés által lefoglalt memóriaméret megegyezik az új típus által igényelttel. Például a
writeln(byte('a'));
utasítás 97-et ír ki, ugyanis az 'a' karakter tároláskor a memóriában egy bájtot foglal, melynek értéke 97 (az 'a' ASCII-kódja).

A számjegy léptetése oda-vissza alakít szám és szöveg között, a 9-ről 0-ra ugrást pedig feltétel helyett a tízes maradék képzésével oldja meg.

Ha elindítod a programot, semmi nem fog történni a gombok nyomogatásakor. Ennek az az oka, hogy a létrehozott gombok OnClick tulajdonságára még nem állítottuk be az eseménykezelő eljárásunkat. Egészítsd ki a gomb tulajdonságainak beállítását a következő sorral:
   g.Font.Size:=18;
   g.OnClick:=@SajatClick;

Egy objektum OnClick tulajdonsága valójában egy mutató, mely az eseménykezelő eljárás memóriacímét kell, hogy tartalmazza. A @ művelet megadja egy azonosító (esetünkben az eseménykezelő eljárás) memóriacímét.
A forráskód az oldal alján letölthető.

Értékelés

Nincs tökéletes program, ez erre a példaprogramra különösen igaz. Érdemes elemezni a fejlesztési lehetőségeket.
Hibalehetőség, hogy "gombosításkor" nem vizsgáltuk a beírt szöveg karaktereit. Ha ezek nem számjegyek, az StrToInt függvény hibaüzenetet ad futáskor.
Ha még egyszer megnyomjuk a gombot, a korábban létrehozott számjegyes gombok megmaradnak, és továbbiak készülnek. Új gombok létrehozásakor a régieket meg kellene szüntetni. Azonban a már létrehozott gombokhoz nem tudunk hozzáférni, mert a g változó mindig csak az éppen létrehozott objektumot mutatja.  Ezeket a mutatókat el kellett volna tárolnunk.

ċ
unit1.pas
(1k)
András Lutter,
2015. aug. 20. 12:45
Comments