In this example, I not only demonstrate the basics of INI Files and working with them, but I also wrap the functionality into its own wrapper. This enables you to encapsulate all fields of the INI file within a common class of your own.
INI File Basics
An INI file is a flat text file which is laid out in a certain standard format. It's used to store light amounts of data, and is quite often used for storing application settings and other various data. It consists of Sections and Values. An INI file can contain a number of unique Sections, and each of those Sections can contain a number of Values. INI Files often use another file extension other than '.ini' for the developer's personalization of their projects.
A Section is enclosed with [ and ] (for example, [MySection]). There cannot be more than one section with the same name within an INI file.
A Value is defined with an = following its value name, and then the actual value after that. Each Value is on its own line, underneath a Section.
There can be any amount of empty lines anywhere within the file - you do not need to keep any certain amount of line breaks.
You can use the same Value Names in different Sections, but not within the same Section.
In Delphi, you use the TIniFile (uses IniFiles unit). This object encapsulates all the INI file functionality for you. The constructor requires the path/filename of the file you would like to work with. If the path specified is invalid (illegal path), or if the path is on a read-only device, then it will fail. If the file does not exist, and it's on a valid path, then the file will automatically be created.
The TIniFile automatically saves the file. However there's another object called the TMemIniFile which I will not go into detail about, but will say that a) it does not automatically save, and b) it keeps its info in the memory until written. The TMemIniFile can be useful if you want to a) Directly read/write to/from the INI file object, AND b) implement saving on demand or even an undo option.
When creating a TIniFile instance, you should enclose it in a try..finally block...
Notice the third parameter in the read functions... This is the default value. If for any reason the value cannot be read, if either it does not exist or if the file is invalid, then the reading function will return this default value instead.
INI Section Lists
You can store a list of sections within an INI file. Although the TIniFile doesn't come equipped with any Section List capabilities, you can still use some things in it to encapsulate your own list.
Take this file for example...
[Settings] SomeProp=Yes AnotherProp=No [Item:0] Name=This is Item 0 Val1=1 Val2=2 [Item:1] Name=This is Item 1 Val1=3 Val2=4 [Item:2] Val1=5 Val2=6
As you see, I'm using a number of sections with their names all 'Item:' + the index. You can start your index from 0 or 1, up to you, but 0 is common and easier. It is not necessarily a requirement to have every single value under each item section (see example below).
In Delphi, things start getting a little interesting. Now you need to use a TStringList to navigate through your section names when reading them...
procedure TForm1.Button3Click(Sender: TObject); var I: TIniFile; L: TStringList; X: Integer; S: String; Z: Integer; T: String; begin I:= TIniFile.Create('C:\MyIniFile.ini'); try L:= TStringList.Create; try I.ReadSections(L); for X:= 0 to L.Count - 1 do begin S:= LowerCase(L[X]); if Pos('item:', S) = 1 then begin T:= S; Delete(T, 1, 5); //Length of 'item:' Z:= StrToIntDef(T, 0); //True index of item ListBox1.Items.Append('Item '+IntToStr(Z)+': '+I.ReadString(S, 'Name', '(No Name)')); end; end; finally L.Free; end; finally I.Free; end; end;
TIniFile.ReadSections() is the magic in this example.
I assume saving a section list in this format should be self explanatory, just reverse-engineer it - do a loop of saving those section names. However, I must also advise that when re-writing an existing file with existing list items, you must first clear all the old ones before saving the new ones - mainly to avoid possible duplicating indexes and stray sections that you don't want to stay. This can be done by using 'EraseSection()' and just provide the name of the section you want erased, along with all its values. You might even want to delete the file and re-write it entirely - it's up to you.
INI Value Lists
Just like the above example for storing Section Lists, you can use a similar approach for storing Value Lists.
procedure TForm1.Button4Click(Sender: TObject); var I: TIniFile; L: TStringList; X: Integer; S: String; Z: Integer; T: String; begin I:= TIniFile.Create('C:\MyIniFile.ini'); try L:= TStringList.Create; try I.ReadSectionValues('MySection', L); for X:= 0 to L.Count - 1 do begin S:= LowerCase(L[X]); if Pos('item:', S) = 1 then begin T:= S; Delete(T, 1, 5); //Length of 'item:' Z:= StrToIntDef(T, 0); //True index of item ListBox1.Items.Append('Item '+IntToStr(X)+': '+I.ReadString('MySection', S, '(No Name)')); end; end; finally L.Free; end; finally I.Free; end; end;
Encapsulating INI File
Now, I will demonstrate how to put all that together into an object with properties.
type TMyIniFile = class(TObject) private FFilename: String; FActive: Bool; FIni: TIniFile; function GetMyString: String; function GetMyInteger: Integer; procedure SetMyString(const Value: String); procedure SetMyInteger(const Value: Integer); public constructor Create(const Filename: String); destructor Destroy; override; published property MyString: String read GetMyString write SetMyString; property MyInteger: Integer read GetMyInteger write SetMyInteger; end;
constructor TMyIniFile.Create(const Filename: String); begin FActive:= False; FFilename:= AFilename; try FIni:= TIniFile.Create(FFilename); FActive:= True; except on e: exception do begin FIni.Free; end; end; end;
destructor TMyIniFile.Destroy; begin if FActive then FIni.Free; inherited; end;
function TMyIniFile.GetMyString: String; begin if FActive then Result:= FIni.ReadString('MySection', 'MyString', '') else Result:= ''; end;
function TMyIniFile.GetMyInteger: Integer; begin if FActive then Result:= FIni.ReadInteger('MySection', 'MyInteger', 0) else Result:= 0; end;
procedure TMyIniFile.SetMyString(const Value: String); begin if FActive then FIni.WriteString('MySection', 'MyString', Value); end;
procedure TMyIniFile.SetMyInteger(const Value: Integer); begin if FActive then FIni.WriteInteger('MySection', 'MyInteger', Value); end;
Notice how I put protection around it using FActive. This is just extra security just in case something went wrong upon creating it. There are a number of errors that can occur, mainly due to invalid filenames being passed to its constructor.
Now you just create an instance if TMyIniFile (passing a valid filename) and use its properties MyString and MyInteger.
That's it for now, I'll probably add more later. NOTE: This code was 98% typed directly into this website. Sorry and please notify me if you find any problems in my sample code.