20. Összetett példa dinamikus memóriakezeléssel
Feladat
Készítsünk programot, amely a formra kattintva pacákat hoz létre, a pacákra kattintva pedig eltünteti azokat! A pacák mozogjanak is a formon!
Terv
A pacákat a form timer eseménye fogja mozgatni, ezért szükséges lesz egy ciklussal végigmenni a pacákon, tehát tárolnunk kell a pacák mutatóit. Erre a célra listát használunk.
A formon való kattintást nem az OnClick
, hanem az OnMouseDown
eseménnyel oldjuk meg, mert utóbbi az egérkoordinátákat is megkapja.
A formszerkesztővel egyedül a Timer1
objektumot hozzuk létre, a pacák mind futási időben készülnek.
A forráskód
unit Unit1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls; type { TForm1 } TForm1 = class(TForm) Timer1: TTimer; procedure FormCreate(Sender: TObject); procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Pacakatt(Sender: TObject); procedure Timer1Timer(Sender: TObject); private var lista:TList; public { public declarations } end; var Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin lista:=TList.Create; end; procedure TForm1.Pacakatt(Sender: TObject); begin lista.Remove(Sender); Application.ReleaseComponent(TShape(Sender)); end; procedure TForm1.Timer1Timer(Sender: TObject); var i:integer; paca:TShape; dx,dy:integer; begin for i:=0 to lista.Count-1 do begin paca:=TShape(lista.Items[i]); dx:=random(3)-1; dy:=random(3)-1; paca.Left:=paca.Left+dx; paca.Top:=paca.Top+dy; if paca.Left<0 then paca.Left:=0; if paca.Left>Width-paca.Width then paca.Left:=Width-paca.Width; if paca.Top<0 then paca.Top:=0; if paca.Top>Height-paca.Height then paca.Top:=Height-paca.Height; end; end; procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var paca:TShape; begin paca:=TShape.Create(Form1); paca.Shape:=stCircle; paca.height:=32; paca.Width:=32; paca.Brush.Color:=128+random(128)+256*(128+random(128))+65536*(128+random(128)); paca.Brush.Style:=bsSolid; paca.Parent:=Form1; paca.Top:=y-paca.height div 2; paca.Left:=x-paca.width div 2; paca.OnClick:=@Pacakatt; lista.Add(paca); end; end.
A forráskód magyarázata: előkészületek
Ha a formszerkesztőt használjuk TShape
létrehozására, a Lazarus a szükséges unitot beilleszti a kódba (Uses ExtCtrls
). Most ezt nekünk kell megtenni. Jó módszer, ha a formszerkesztővel létrehozunk egy TShape
-et, majd rögtön töröljük: ebből kiderül, mi a szükséges unit.
Hasonlóképpen, a pacára kattintás eseménykezelőjének fejlécét is kipuskázhatjuk a Lazarusból, ha a szerkesztővel hozunk létre eseménykezelőt. Az egyes pacák eseménykezelője is legyen a Lazarus filozófiája szerint a TForm1
osztály eljárása; ezt most nekünk kell beírni a TForm1
osztály típusdeklarációjába.
procedure Pacakatt(Sender: TObject);
A szerkesztővel hozzuk létre a form OnCreate
és OnMouseDown
eseménykezelőjét!
A globális lista
Egyetlen globális változónk a lista, amely a paca objektumok mutatóit tárolja. Mivel az összes paca a formon belül van, ezt az objektumot is a TForm1
osztályban deklaráljuk, ráadásul a private
szekcióban, mert csak a form eseménykezelői fogják használni.
A listát létre kell hoznunk a program indulásakor:
procedure TForm1.FormCreate(Sender: TObject); begin lista:=TList.Create; end;
Pacák létrehozása
Paca a formra kattintáskor keletkezik. A Form1MouseDown
megkapja az egérmutató X
és Y
koordinátáit, ráadásul a form bal felső sarkához képest. Az eseménykezelő segédváltozója a paca
, amely az éppen létrehozott TShape
objektumra mutat.
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var paca:TShape;
A most létrehozott objektum minden lényeges tulajdonságát itt kell beállítani. Először létrehozzuk az objektumot:
paca:=TShape.Create(Form1);
majd beállítjuk a tulajdonságait. A Parent
felelős azért, hogy a paca a Form1
-en belül jelenjen meg. A színek beállítása véletlenszerű. A helyzetnél ügyelünk arra, hogy a Left
és Top
a bal felső sarok koordinátáit adja meg, mi viszont az egérrel a kör közepét jelöljük ki:
paca.Shape:=stCircle; paca.height:=32; paca.Width:=32; paca.Brush.Color:=128+random(128)+256*(128+random(128))+65536*(128+random(128)); paca.Brush.Style:=bsSolid; paca.Parent:=Form1; paca.Top:=y-paca.height div 2; paca.Left:=x-paca.width div 2;
végül beállítjuk az eseménykezelőt, és a létrehozott objektum mutatóját eltároljuk a listában:
paca.OnClick:=@Pacakatt; lista.Add(paca);
Objektum törlése
A Pacakatt
eljárásban Sender
paraméter a kattintott paca mutatója. Mi történne, ha a pacát TShape(Sender).Destroy
metódusával törölnénk?
A programunk futási hibával leállna (érvénytelen memóriaterületre hivatkozás). Ha ugyanis a pacára kattintunk, lefut az OnClick
eseményen kívül az alapértelmezett OnMouseDown
és OnMouseUp
esemény is, utóbbi az OnClick
után. Ha viszont az OnClick
már törölte az objektumot, az OnMouseUp
érvénytelen hivatkozást tartalmaz (a Sender
paramétere olyan memóriaterületre mutat, ami már nincs lefoglalva).
Megtehetjük, hogy az utolsónak lefutó OnMouseUp
eseményt használjuk. Ez azért lenne rossz gyakorlat, mert nem fogjuk tudni minden egyes objektumnál követni, melyik a legutolsó eseménykezelője.
Ezért használjuk az Application.ReleaseComponent
eljárást, amely az objektumot törlésre jelöli, de csak az eseménykezelők lefutása után törli. Ha egy objektumot a saját eseménykezelőjében törlünk, ez a javasolt módszer.
Törlés előtt még az objektum mutatóját ki kell venni a listából:
procedure TForm1.Pacakatt(Sender: TObject); begin lista.Remove(Sender); Application.ReleaseComponent(TShape(Sender)); end;
Figyeljük meg a típuskényszerítés használatát: a lista csak egyszerű mutatókat tartalmaz, a fordító nem tudja, milyen típusú objektumra mutat!
Mozgatás
A Timer1
egy ciklussal végigmegy a listán (ne feledjük, hogy 0-tól indexelt!), és az egyes pacákat elmozdítja a 8 irány egyikébe (vagy helyben hagyja). Gondoskodnunk kell arról is, hogy a pacák ne lépjenek ki a formból.
procedure TForm1.Timer1Timer(Sender: TObject); var i:integer; paca:TShape; dx,dy:integer; begin for i:=0 to lista.Count-1 do begin paca:=TShape(lista.Items[i]); dx:=random(3)-1; dy:=random(3)-1; paca.Left:=paca.Left+dx; paca.Top:=paca.Top+dy; if paca.Left<0 then paca.Left:=0; if paca.Left>Width-paca.Width then paca.Left:=Width-paca.Width; if paca.Top<0 then paca.Top:=0; if paca.Top>Height-paca.Height then paca.Top:=Height-paca.Height; end; end;
A teljes program itt letölthető:
pacak.lpi
pacak.lpr
unit1.lfm
unit1.pas