Minden valamire való program megkíméli a felhasználót attól, hogy az egyszer már jól beállított paraméterek újra és újra meg kelljen adni minden futtatás során. Igaz, vannak kisebb programok, ahol erre nincs szükség, de a legtöbb esetben erre módot kell adni. Tekintsük most át, miként lehet elkészült programunkba olyan kódot illeszteni, mely megjegyzi az aktuális beállításokat.
Alapvetően két módszer áll rendelkezésünkre. Az egyik – klasszikusnak mondható – módszer az INI fájlok használata, a másik modernebb a Windows Registry. Nézzük meg először az INI fájlok kezelését.
Az INI fájlokról sokan – szerintem tévesen – úgy beszélnek, mint egy idejét múlt módszerről, aminek alkalmazása mondhatni megmosolyogtató. Mégis számos olyan esetet lehet felhozni, amelyben a Registry vs. INI párbajban az INI fájlok kerülnek ki győztesen. Elsősorban talán mobilitása az, ami miatt néha előnyösebb használatuk. Például egy menüt, a menüpontok elrendezését célszerűbb egy INI fájlba tárolni, mert a program – esetleg a számítógép – újratelepítése után egy mozdulattal helyreállítható a korábbi állapot. Azok számára, akik ismerik a Registry-t – akik nem, azoknak legközelebb egy gyorstalpalóval próbálok meg segíteni – tudják, hogy onnan ki lehet bizonyos részeket exportálni, és aztán újra importálni, így a fent leírtak akár a Registry-vel is megoldhatóak. Igen ám, de ehhez kell a működő Windows! Egy újratelepítés során azonban nem, illetve csak nehezebben tudjuk kiexportálni egy másik gépen a Registry tartalmát, mint egy egyszerű INI fájlt átmásolni. Egy szó mint száz, pro és kontra mondhatunk véleményeket, úgy is a gyakorlat igazolja ezeket.
Az INI fájl egy egyszerű ASCII szövegfájl előre meghatározott felépítéssel.
Bizonyára mindenki nézelődött már a gépén és talált ilyen fájlokat, hogy win.ini vagy system.ini. Ezeket szemügyre véve a következőket vehetjük észre:
Egy INI fájlban név=érték párok szerepelnek, egy sorban egy ilyen érték pár:
…
Maximized=No
Left=96
Top=45
Right=1133
…
A név tetszőleges karakterlánc lehet, melyben lehetőség szerint az angol ABC betűit használjuk csak, az esetleges hibák elkerülése végett. Az érték is tulajdonképpen karakterláncként tárolódik el, függetlenül valódi adattípusától (integer, boolean és tsa.), de mint azt majd később látni fogjuk, ilyen típusok használatára is ad megfelelő utasításokat a Delphi.
Ezeket a név=érték párokat szekciókba rendezzük. Egy szekció egy gyűjtő csoport, melyen a logikailag összetartozó név=érték párokat helyettük el:
…
[Position]
Maximized=No
Left=96
Top=45
Right=1133
[Settings]
Version=205
Start=Yes
…
Ebben a példában két szekciót láthatunk, az egyik neve Position, a másik Settings. Ebből is látható, hogy a szekcióneveket [ ] közé írva tárolja a rendszer.
Egy INI fájlban tetszőleges számú üres sor lehet, ezeket figyelmen kívül hagyja a rendszer. Ha egy sort „;” jellel kezdünk, akkor azt a sort „kikommentezzük”, vagyis olyan lesz, mintha ott se lenne.
Delphiben a TIniFile osztály segítségével férhetünk hozzá az ilyen fájlokban tárolt információkhoz. Ez nem egy komponens, így ne is keressük a komponens palettán. Használatához az inifiles unit-ot kell a program uses sorához hozzávenni, majd magunk kell létrehozni és felszabadítani (!) az osztályt. Igen, felkiáltójellel írtam a felszabadítást, mivel sokan ezt figyelmen kívül hagyják. A TIniFile osztálynak nincsen Owner vagy Parent tulajdonsága, egyetlen Form vagy más osztály sem fogja meghívni a Free metódusát, ha mi nem tesszük meg.
Az osztályt létrehozó konstruktor:
constructor Create(const FileName : String);
Paraméterként meg kell adnunk a INI fájl nevét, teljes elérési úttal. Ezek után ezzel az osztálypéldánnyal ezt az INI fájlt tudjuk kezelni.
Most nézzük meg, miként tudunk különböző típusú értékeket kiolvasni az így megnyitott fájlból. Mint azt fentebb olvashattuk, az értékek karakterláncként tárolódnak, mégis van mód más típusú értékek kezelésére. A szükséges konverziókat a rendszer a háttérben elvégzi. Minden olvasó metódusnak meg kell adnunk, hogy melyik szekcióból olvasson, meg kell természetesen adni magának az értéknek a nevét és egy alapértelmezett értéket, melyet akkor kapunk, ha valamilyen hiba folytán nem járt eredménnyel az olvasás. Ilyen, ha az érték vagy szekció nem létezik, az értékhez nincs adat rendelve (Érték=<üres>) vagy a kért érték nem megfelelő adattípust tartalmaz (pl. Integer értéket akarunk beolvasni egy alfanumerikus karaktereket is tároló értékből). Ezek után nézzük az ide vonatkozó metódusokat:
function ReadBool (const Section, Ident: String; Default: Boolean): Boolean;
function ReadDate (const Section, Ident: string; Default: TDateTime): TDateTime;
function ReadDateTime (const Section, Ident: String; Default: TDateTime): TDateTime;
function ReadFloat (const Section, Ident: String; Default: Double): Double;
function ReadInteger(const Section, Ident: String; Default: Longint): Longint;
function ReadTime (const Section, Ident: String; Default: TDateTime): TDateTime;
function ReadString(const Section, Ident, Default: string): string;
Minden metódusnál a Section paraméter azonosítja a szekciót, az Indent az érték nevét, a Default pedig a már említett alapértelmezett értéket.
Ha már olvasni tudunk egy INI fájlból, nézzük meg, hogyan tudjuk tartalmát módosítani:
procedure WriteBool(const Section, Ident: String; Value: Boolean);
procedure WriteDate(const Section, Ident: String; Value: TDateTime);
procedure WriteDateTime(const Section, Ident: String; Value: TDateTime);
procedure WriteFloat(const Section, Ident: String; Value: Double);
procedure WriteInteger(const Section, Ident: String; Value: Longint);
procedure WriteTime(const Section, Ident: String; Value: TDateTime);
procedure WriteString(const Section, Ident, Value: string);
Azt hiszem különösebb magyarázatot egyik se igényel.
Ha egy olyan értéket szeretnénk írni, amely nem létezik, vagy egy még nem létező szekcióba szeretnénk írni, akkor a rendszer minden visszajelzés nélkül létrehozza ezeket és beállítja a kívánt értéket.
Lehetőségünk van arra, hogy leellenőrizzük, hogy egy szekció vagy érték létezik-e egyáltalán:
function SectionExists (const Section: String): Boolean;
A metódus a paraméterében megadott szekciónevet ellenőrzi, hogy létezik-e a Create konstruktorban megadott fájlban. Igaz (true) értékkel tér vissza, ha létezik, hamissal (false), ha nem.
Hasonló módon használható a következő metódus is, mely a megadott szekcióban ellenőrzi egy érték létezését.
function ValueExists (const Section, Ident: String): Boolean;
Ha egy értékre már nincs szükségünk, törölhetjük véglegesen a
procedure DeleteKey(const Section, Ident: string);
metódussal. Ha pedig egy teljes szekciótól szeretnénk nyom nélkül megszabadulni, használjuk az
procedure EraseSection(const Section: string);
metódust, melynek csak meg kell adni, hogy mely szekciót törölje és már meg is szabadít minket tőle.
Windows 9x rendszereken az operációs rendszer az INI fájlokat ún. bufferek segítségével kezeli, hogy ezzel is gyorsítsa a program működését. Ennek lényege, hogy egy írási, törlési parancs kiadásakor nem történik meg a művelet azonnal a fájlban, csak a memóriában. A tényleges írás csak egy rövid idő múlva történik meg, de ebből a program csak annyit lát, hogy az adott művelet végre lett hajtva a kérés pillanatában. Ha a lemezre írás és a parancs kiadása között például elmegy az áram, vagy bármi olyan történik, amely az operációs rendszer működését megszakítja, akkor a kért írás/törlés hatása elvész, mintha ki sem adtuk volna a parancsot. Ha bizonyosak akarunk lenni abban, hogy ez a memória buffer kiíródik a háttértárra, hívjuk meg az
procedure UpdateFile;
metódust, mely kiüríti ezeket a buffereket. Windows NT és az őt követő rendszereken (Windows 2000/XP/.NET) erre nincs szükség, mert ezek az operációs rendszerek nem bufferelik az INI fájlokat.
Összefoglalásképpen most nézzünk egy példát, mely a fenti metódusok használatát mutatja be.
Indítsunk egy új projektet és dobjunk két gombot a megjelenő Form-ra. A két gomb OnClick eseményeihez a következőket írjuk be:
procedure TForm1.Button1Click(Sender: TObject);
var
EnIniFajlom : TIniFile;
begin
EnIniFajlom := TIniFile.Create('C:\tesztini.ini');
try
with EnIniFajlom do
begin
WriteString('Window','Caption','Program');
WriteInteger('Window','Left',50);
WriteInteger('Window','Top',150);
WriteString('Button1','Caption','Írogatás');
WriteInteger('Button1','Left',10);
WriteInteger('Button1','Top',30);
WriteString('Button2','Caption','Olvasgatás');
WriteInteger('Button2','Left',10);
WriteInteger('Button2','Top',60);
end;
finally
EnIniFajlom.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
EnIniFajlom : TIniFile;
begin
EnIniFajlom := TIniFile.Create('C:\tesztini.ini');
try
with EnIniFajlom do
begin
Caption := ReadString('Window','Caption',Caption);
Left := ReadInteger('Window','Left',Left);
Top := ReadInteger('Window','Top',Top);
Button1.Caption := ReadString('Button1','Caption',Button1.Caption);
Button1.Left := ReadInteger('Button1','Left',Button1.Left);
Button1.Top := ReadInteger('Button1','Top',Button1.Top);
Button2.Caption := ReadString('Button2','Caption',Button2.Caption);
Button2.Left := ReadInteger('Button2','Left',Button2.Left);
Button2.Top := ReadInteger('Button2','Top',Button2.Top);
end;
finally
EnIniFajlom.Free;
end;
end;
A programmal létrehozhatunk egy INI fájlt, melyben az ablak és a két gomb pozícióját és feliratát tároljuk el. A második gombra klikkelve az így kiírt adatokat olvassuk be és alkalmazzuk őket.
Ha anélkül klikkelünk a Button2-re, hogy előtte a Button1-re klikkeltünk volna, akkor nem fog semmi sem törénni. Miért? Azért, mert a ReadString és ReadInteger metódusoknak alapértelmezett értékként a módosítani kívánt értéket adjuk vissza, vagyis ha nem létezik még a fájl, akkor például az ablak címsoraként annak régi értékét állítjuk be. Ha létrehozzuk az INI fájlt, így fog kinézni:
[Window]
Caption=Program
Left=50
Top=150
[Button1]
Caption=Írogatás
Left=10
Top=30
[Button2]
Caption=Olvasgatás
Left=10
Top=60
Próbálkozzunk a metódusokkal. Gyakorlásképpen mentsük el a gombok szélességét is (és persze olvassuk is vissza), valamint tároljuk el az utolsó módosítás dátumát és visszaolvasáskot jelenítsük meg egy TLabel komponensen. Ha valaki esetleg elakadna, bátran írjon egy e-mail nekem és megbeszéljük, hogy hol akadt el és mi lehet a megoldás.
Legközelebb megismerkedünk a Windows Registry-vel és annak használatával.
Geiger Tamás info@gsc.hu