2010. január 8., péntek

Personal settings and ini-files


Problem/Question/Abstract:

This article illustrates the usage of the TInifile object, and gives guideline when and how to use ini files for the storage of personal settings.

Answer:

This article is part of a series of five articles about preserving user sensitive settings.

INI-files are meant to retain settings between instances of your applications. Their structure is very simple, which limits their functionality.

This article explains the structure of ini files, and the basics of how to read and write them from Delphi.

Structure of ini files

Lets first have a look at the structure of ini-files. Basically, an ini file has a number of blocks, enclosed in square brackets, and every block has some settings.

Example: here is a part of my W2000 win.ini file:

[WinZip]

version=6.1-6.2

Note-1=This section is required only to install the optional WinZip Internet Browser Support build 0231.

Note-2=Removing this section of the win.ini will have no effect except preventing installation of WinZip Internet Browser Support build 0231.

win32_version=R6.3-7.0

[Solitaire]

Options=91

[MSUCE]

Advanced=0

CodePage=Unicode

Font=Arial

[MAPI 1.0 Time Zone]

Bias=0

StandardName=GMT Standard Time

StandardBias=0

StandardStart=00000A00050002000000000000000000

DaylightName=GMT Daylight Time

DaylightBias=ffffffc4

DaylightStart=00000300050001000000000000000000

ActiveTimeBias=0

We have blocks between square brackets, such as {WINZIP], [Solitaire], and [MAPI 1.0 Time Zone]. Winzip has 4 data items, version, Note-1, Note-2 and win32_version.

The data behind an item name can be alphabetical, numeric or boolean. Binary data is limited to those data which does no contains a #0 or CR/LF. Blank lines may be used between the blocks.

As you can see above, many applications use the win.ini file to store settings-information. You are free to choose the win.ini file. If you have just a few settings, this may be the right choice. Other applications should not suffer in any way. If you have more than one block of information, it is preferable to define your own ini file.

Writing ini-files from Delphi

Delphi provides us with a TIniFile object. This object is defined in the unit ini-files. Add this unit to your uses clause. Then create :

lIniFileVar := TIniFile.create(FileName);

You may or may not include a path with the filename. If you don't, windows will assume it must be created in the windows directory. This is the default. By using the ExtractFileDir(Application.Exename), you can easily create ini-files in the directory in which your application is created. Simply pass the entire path with the file name.

If the file already exists, windows will open it. If it does not, windows will create it.

The next thing you will want is to write some information to it. We will construct a small demo application. Start your Delphi, choose new application, and save your form as formDemoIniFile, and your project as DemoIniFile. Put a textbox, a SaveFile dialog and an OpenDialog component on your form. Next, drop two buttons on your form, and call them btnExit and btnOpenFile.

In the btnOpenFileClick event, write:

procedure TForm1.btnFileOpenClick(Sender: TObject);
var
  lIniFileVar: TIniFile;
begin
  OpenDialog1.Filter := 'Text files |*.txt|All files|*.*';
  if OpenDialog1.execute then
  begin
    edit1.text := OpenDialog1.FileName;
    lIniFileVar := TIniFile.create('DemoApp.ini');
    lIniFileVar.WriteString('OPENEDFILES', 'OPENDIALOG1', edit1.text);
    lIniFileVar.WriteString('OPENEDFILES', 'OPENDIALOG1LASTDIR',
      ExtractFileDir(edit1.text));
    lIniFileVar.free;
  end;
end;

lIniFileVar is a local variable in this routine of th type TIniFile. When we create it, we pass the filename, in this case DemoApp.ini. Next we use the WriteString method to write the contents of to the edit1.text to the inifile. We specify this string must be stored in the block OPENEDFILES and that the item name = OPENDIALOG1. next we also write the directory.

After we have run this program, the result might look:

[OPENEDFILES]

OPENDIALOG1=E:\program files\delforex\License.txt

OPENDIALOG1LASTDIR=E:\program files\delforex

Writing numeric data is essentially the same, and so is writing booleans.

Reading them from Delphi

Of course we gain nothing when we can write data but cann't read them. So we expand our example a bit with a few lines to read the previous data before we present the OpenFileDialog.

procedure TForm1.btnFileOpenClick(Sender: TObject);
var
  lIniFileVar: TIniFile;
begin
  // read old data and assign them to OpenFile dialog.
  lIniFileVar := TIniFile.create('DemoApp.ini');
  OpenDialog1.FileName := lIniFileVar.ReadString('OPENEDFILES', 'OPENDIALOG1', '');
  OpenDialog1.InitialDir := lIniFileVar.ReadString('OPENEDFILES',
                 'OPENDIALOG1LASTDIR', '');
  lIniFileVar.Free;
  OpenDialog1.Filter := 'Text files |*.txt|All files|*.*';
  // ask user to open file
  if OpenDialog1.execute then
  begin
    edit1.text := OpenDialog1.FileName;
    // Store new file data in ini file.
    lIniFileVar := TIniFile.create('DemoApp.ini');
    lIniFileVar.WriteString('OPENEDFILES', 'OPENDIALOG1', edit1.text);
    lIniFileVar.WriteString('OPENEDFILES', 'OPENDIALOG1LASTDIR',
      ExtractFileDir(edit1.text));
    lIniFileVar.free;
  end;
end;

Note that the ReadString Function requires a third argument, this is the default value. Note that one may use the ReadSectionValues (const Section: string; Strings: TStrings) method to read all values of an entire section.

Hacking delphi

There are some circumstances in which you might want to read an entire block (also called 'section'). If you wish to use this function, some Delphi hacking might be useful. By default, the buffer for the reading sections is 16K. You can upgrade this to 32K no problem.  

Simply start Delphi, open \Program Files\Borland\Delphi5\Source\Vcl\inifil.pas, and look for the ReadSection and ReadSections procedures. Both have a constant :

BufSize = 16384;

Change this constant to 32768 and you claim double the amount of memory.

When you study this unit, you will find that all methods boil down to usage of the windows WritePrivateProfileString and GetPrivateProfileString functions.

The unit has no WriteSectionValues procedure. Should you wish, it can be easily added.
    
procedure TCustomIniFile.WriteSectionValues(const Section:
  string; Strings: TStrings);
var
  KeyList: TStringList;
  i: Integer;
begin
  KeyList := TStringList.Create;
  for i := 0 to Strings.Count - 1 do
  begin
    WriteString(Section, Strings.Names[i],
      Strings.Values[Strings.Names[i]]);
  end;
end;

Alternative

There is an alternative for the usage of the TInifile object. Any TStringList has a LoadFromFile and SaveToFile method. Using the Values property, one could extract item values from them, and even change them. But as these methods do not adhere to the windows api's and their rules about file locations, this practice is not recommended. Also, as the Values property does not support usage of sections, this may lead to problems with duplicate item names.

Conclusion

You now know how to use ini-files. You should also be aware of its possibilities. As for its limitations: Don't try to store binary data. Neither store strings which contain a CR/LF, as your values can be just 1 line of  length..

Nincsenek megjegyzések:

Megjegyzés küldése