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

Előző     Tartalom