Delphi - A Delphi kiegészítése: TCollection varázsló a forráskód szerkesztőhöz

forráskód letöltése
Amikor olyan komponenst készítünk, melyben van egy vagy több TCollection-ból származó property, akkor elég bosszantó, hogy mindig ugyanazokat az eljárásokat, függvényeket, stb. kell begépelnünk, némi változtatással. Sokkal egyszerűbb lenne, ha csak meg kellene adnunk az új osztály nevét, a property-ket, azok típusát, és egy kattintással létrejönne a forráskód a TCollectionItem és a TOwnedCollection osztályokból származó új property-khez. Természetesen mindez az éppen szerkesztett forráskódba kerülne be úgy, hogy akár módosítás nélkül is fordítható, és működőképes legyen. A mellékelt példaprogram megnyitása előtt a CollEdit.pas-ban lévő komponenst telepítenie kell a Delphi alá.

A bevezetőben megfogalmazott igény mostantól nem álom. A mellékelt program a telepítés után beépül a Delphi-be, bármikor menüből előhívható, és nagyon egyszerűen használható. Az Animare menüpont alatt létrejön egy új menüpont Collection Wizard névvel. Ha az Animare menü még nem létezett, akkor természetesen az is létrejön.

A Delphi kiegészítéséről már volt szó néhány korábbi cikkben, ezért ezzel itt most nem foglalkozunk. Vizsgáljuk inkább meg a program lényegi részét, amely a megadott adatokból előállítja a forráskódot.

A dialógus ablakon a következő komponensek találhatók:
  • Edit1: az új TCollectionItem-ből származó osztály neve (például TUjCollectionItem).
  • ListView1: a property-k listáját tárolja. Két mezője van, az elsőben a property neve (ami az Object Inspectorban megjelenik), a második a property típusa.
  • SpeedButton1: új property felvételére szolgál.
  • SpeedButton2: a kiválasztott property-t törli a listából.
  • Edit2: a property neve. A nevet úgy kell megadni, ahogyan az megjelenik Object Inspectorban!
  • ComboBox1: a property típusa. Csak a ComboBox-ban felsorolt típusokat lehet megadni!
  • Button1: forráskód generálása.
  • Button2: "Mégsem" gomb.
A forráskód generálása tehát a Button1 OnClick eseményének bekövetkezésekor történik meg. Mivel ebben elég sok változót deklaráltunk nézzük meg ezek jelentését:
  • CurrentFile: az aktív forráskód fájl-neve.
  • ModuleInterface: TModuleInterface típusú objektum, amely a TEditorInterface osztály lekérdezéséhez kell.
  • EditorInterface: TEditorInterface osztály, amelyen keresztül hozzáférhetünk a megnyitott forráskódokhoz.
  • EditView: TIEditView típusú objektum. Néhány függvényre szükségünk lesz belőle.
  • EditReader. TIEditReader típusú objektum, ami a forráskód beolvasásához szükséges.
  • EditWriter: TIEditWriter típusú objektum, ami a forráskód módosításához szükséges.
  • Text: ebben a változóban fogjuk tárolni az éppen beolvasott sort.
  • Line: a beolvasott sor sorszáma.
  • NoLines: a sorok száma. A forráskód módosítása során ennek folyamatosan változik az értéke.
  • p, endp: a sor kezdő és befejező karakterpozíciója.
  • LineLen: a sor hossza.
  • pos: TCharPos típusú változó, amely a beolvasáshoz és a változtatásokhoz kell.
  • epos: TEditPos típusú változó, amely ahhoz szükséges, hogy a forráskód módosításakor a megfelelő pozícióba kerüljön a beszúrt sor.
  • code: TStringList típusú objektum, amelyben előállítjuk a beszúrandó forráskódot.
  • i és t: ciklusváltozók.
  • Decl, Impl: logikai típusú változók, melyek megadják, hogy a deklarációs illetve implementációs részt beszúrtuk-e már a forráskódba.

Beállítjuk a Line kezdősorszámot 1-re, a NoLines változóban eltároljuk a forráskód sorainak számát, és a Decl és Impl változóknak hamis értéket adunk:
  Line:=1;
  NoLines:=EditorInterface.LinesInBuffer;
  Decl:=false;  Impl:=false;
Egy ciklust indítunk, amely addig tart, amíg végig nem értünk a teljes forráskódon.
  while Line<=NoLines do begin
Lekérdezzük a szükséges objektumokat. Felmerülhet a kérdés, hogy ezt miért nem a ciklus előtt tesszük meg. Ha így járnánk el, akkor a forráskód módosítása után nagyon furcsa eredményt kapnánk, ezért ezeket az objektumokat mindig lekérdezzük, majd a ciklusmag végén megszüntetjük.
    EditView:=EditorInterface.GetView(0);
    EditReader:=EditorInterface.CreateReader;
    EditWriter:=EditorInterface.CreateUndoableWriter;
Az aktuális sor beolvasása nem olyan egyszerű, mint ahogy első hallásra gondolnánk, ugyanis a fenti osztályokból valamilyen okból kifolyólag hiányoznak az ehhez szükséges függvények. Míg a sorok számát többféleképpen is meg tudjuk határozni, addig egy sor beolvasását egy trükkel kell megoldanunk. A trükk a következő: a pos változóban beállítjuk az aktuális sor 1. karakter pozícióját, majd a CharPosToPos függvénnyel lekérdezzük ennek tényleges pozícióját, amit a property változóban tárolunk. A következő lépésben ugyanezt tesszük, de a következő sor első karakterének pozícióját kérdezzük le, amit az endp változóban tárolunk. A LineLen változóban eltároljuk a két pozíció különbségét, ami az aktuális sor hosszát adja meg. Ezután a Text változó hosszát beállítjuk erre a hosszra, majd az EditReader objektum GetText függvényével kiolvassuk a sor tartalmát a Text változóba.
    pos.CharIndex:=0;  pos.Line:=Line;
    p:=EditView.CharPosToPos(pos);
    pos.Line:=Line+1;
    endp:=EditView.CharPosToPos(pos);
    LineLen:=endp-p-2;
    SetLength(Text, LineLen);
    EditReader.GetText(p, PChar(Text), LineLen);
Most már megvan az aktuális sorunk, ami annak eldöntéséhez kell, hogy az új kódrészleteket hová szúrjuk be. A deklarációt a type foglalt szó után kell beszúrni, ezért ellenőrizzük, hogy az aktuális sorban ez található-e. Ha igen, és Decl változó értéke hamis, akkor létrehozzuk az új kódot, és beszúrjuk az aktuális sor után. A Decl változóra azért van szükség, mert type kulcsszó a forráskódban több helyen is lehet, például az implementációs részben is előfordulhat.
    if (UpperCase(Trim(text))='TYPE') and (not Decl) then begin
      Decl:=true;
A Code változóban létrehozzuk a beszúrandó forráskódot. Ehhez természetesen felhasználjuk a ListView1 komponensben tárolt property-k adatait. Itt térnénk ki a tab1 és tab2 konstansok jelentésére. Ezek 2, illetve 4 szóközt tárolnak, tehát a forráskód tagolása úgy történik, mintha a tabulátor pozíciók 2 karakterenként lennének meghatározva. Aki nem ilyen beállításokkal használja a Delphi-t, az nyugodtan módosíthatja a tab1 konstanst, akár valódi tabulátorjelre (#9) is. Ennek következtében a tab2 is módosul (tab2=tab1+tab1).

Az implementációs részt a program utolsó sora elé szúrjuk be. Arra vigyázzunk, hogy ha az "end." után üres sorok vannak, akkor az "end." után kerül a forráskód, és ez hibát okoz. Az "end." után csak akkor lehetnek üres sorok, ha mi hoztuk létre azokat, tehát ez nem valószínű.

A ciklusmag végén növeljük a beolvasandó sor sorszámát, majd a NoLines változóban újra eltároljuk a sorok számát, mivel ez az új kódrészletek beszúrása után megváltozott. Töröljük az EditReader és EditWriter objektumokat, melyeket a ciklusmag elején újra létre fogunk hozni.