HOME  - -  Lazarus/ Delphi tutorials

Using ini files

"Old Skul"... but robust
Easy configuration of apps, and other uses

(Page's URL: ldn150.htm)

I've written about using ini files before: https://sheepdogguides.com/lut/lt2Nc.htm (Sept 13... and I don't think that was the first time!)

The page you are reading now was created 24 Jun 22. It is intended to be a "short sharp" version. But still one that someone starting from scratch with ini files can understand. (Some experience of the basics of Lazarus is expected, if you want to use an ini file in a Lazarus app.)

If you "know all about" using ini files, and just want a quick checklist of the bits you have to put into an app's code, to make it use ini files, I have done one, based on the suggestions in the page you are reading.

At the end of the page, there is a link which will give you the whole project in a zip file.

What is an ini file?

Let's start by looking at .ini files in general... for anyone using Windows.

Then I'll talk a about "doing ini files" in a Lazarus app.

I will show you a very basic ini file use, to get you started, and then expand that.

You can skip the general introduction if you wish.

The expansion will round things out with a few "frills" to make it more user friendly, more polished. But by starting with the most basic use of an ini file, I hope I will lead you to full understanding of how to do it without too many "cliffs" to "climb" on your learning curve.

What are ini files? What are they for?

Ini files have a long and glorious history. Nowadays, programmers often use "the registry" for the things I like done with an ini file. Why? Not necessary!

I like to stay away from the registry. Problems there, mistakes while trying to correct them, can wreak havoc across all of your use of the computer.

An ini file is just a small file with some text in it. It affects nothing that doesn't explicitly seek it out.

Wikipedia has a good entry on ini files.

There are some very simple syntax rules governing what goes into an ini file.

Here's a sample ini file...

;Window size/ position
[AppWindow]
top=100
left=200
width=400
height=300
;
;Terms be added/multiplied
[Terms]
FirstTerm=7
SecondTerm=5

If I wrote an app using that, and called the app "ldn150.exe", I'd call the file "ldn150ini.txt"... but you an call it pretty well anything. ("LDNxxx" ???It comes from "Lazarus Demo, New series, serial xxx"

To illustrate the use of the ini file I'll prepare a (nearly pointless) little app. It will display the sum and product of any two integers supplied by the user via edit boxes.

The ini file above, with the code I'll give you in a moment, will make the app come up in a certain size, at a certain place, on your screen, with the initial integers set to 7 and 5.

In the simple scheme of using ini files that this tutorial begins with, you'd create that file with a text editor, e.g. Notepad, or Textpad.

The syntax rules...

In an ini file, every line begins with...

Lines that begin with A semicolon ; are comments... they are for the human users of the ini file.

Lines that begin with An open-square-bracket [ will then have a "word", followed by a close-square-bracket (]). These are called the "sections" of the ini file. (I'll say more about these later.)

Lines that begin with something elsewill then have a "word", followed by an equals sign (=) followed by a value. They are "keys". (Or properties.) I'll say more about these later, too!

In the example I gave, there are two "sections"...

[AppWindow]
[Terms]

And in the AppWindow "section" there are four "keys"

top=100
left=200
width=400
height=300

(The Terms section has two keys. Obvious, I hope?)

So what's it good for?

Please remember that we are starting "simple". What follows immediately is not all there is to ini file use!

Write the app properly, and as it launches, it will look for an ini file. What ini file to look for, in the simple case, is rather rigidly specified in the app.

If the app finds the ini file, it reads it. And it uses the contents as "input" to how the app should run.

(If it doesn't find it, or doesn't find in it a section/key item it expects, it uses default values, "as if" it found the section/key item. The default values are hard coded into the app.)

Let's do it

Forget ini files for a moment.

Build a little app.

The app needs two edit boxes, two labels.

(Some other "put in any app" items may creep into the sample code here which you may notice later, but for our wants a two edit boxes/ two labels app is "enough".)

(My top "put in every app" items are discussed in my "Put In Every Lazarus App"), which I commend to you.)

Call the edit boxes eFirstTerm and eSecondTerm (bad names!), and the labels laSum and laProduct.

Put code into FormCreate to put zeros into all four when the app starts up....

procedure Tldn150F1.FormCreate(Sender: TObject);
begin
  eFirstTerm.text:='0';
  eSecondTerm.text:='0';
  laSum.caption:='0';
  laProduct.caption:='0';
end;

So far, so good!

Don't worry... I haven't forgotten that this is about ini files. But first:

Make it "work"...

Create a sub-routine called "DoIt"...

procedure Tldn150F1.DoIt;
begin
   laSum.caption:=inttostr(strtoint(eFirstTerm.text) + strtoint(eSecondTerm.text));
end;

(This tutorial is meant for "intermediate" Lazarus programmers. I won't (after this!) mention things like "Of course, you'll need to put 'procedure DoIt;' in the 'private' (or 'public') block of the interface to make that work.")

Double click on eFirstTerm to give yourself a eFirstTermChange handler skeleton. Make it call "DoIt".

Double click on eSecondTerm to give yourself a eSecondTermChange handler skeleton. Make it call "DoIt" too. (Or use the fancier, more elegant alternative if you know how.)

Run your code.

It doesn't handle input to the edit boxes that is not digits elegantly. It doesn't calculate the product... yet. But it should "work", apart from that.

The app is also ungracious if you clear either edit box "en route" to a new number.

You change the numbers in the edit boxes, and the sum of the numbers appears in the first label.

Add the following to "DoIt" to get the product as well as the sum...

laProduct.caption:=inttostr(strtoint(eFirstTerm.text) * strtoint(eSecondTerm.text));

As for the problems that arise when the edit boxes do not hold digits and only digits... they are not related to using ini files, and are for another day!

TARGET FOR "Start here"...

Back to ini files!...

Don't worry for the moment that we have not yet created the ini file this app will use! (If you have made one already, for the moment change the name to something other than "ldn150ini.txt".)

Put "IniFiles" in the app's "uses" clause.

Create a FormActivate procedure, i.e. an event handler for the "OnActivate" event. (I'll explain why you need both a FormCreate and a FormActivate in a moment.)

For now, make it...

procedure Tldn150F1.FormActivate(Sender: TObject);
var dfIniFile:TIniFile;//"df" in name from "datafile". Could be anything.
begin
   //Heart of reading from ini file
  dfIniFile:=TIniFile.Create('ldn150ini.txt');

  with dfIniFile do begin
     eFirstTerm.text:=readstring('Terms','FirstTerm','999');
     eSecondTerm.text:=readstring('Terms','SecondTerm','998');
     end;//with...

  dfIniFile.Free;

  //End of "Heart of...."

  DoIt;

end;

Run that.

If no ini file of the name given is found, eFirstTerm.text will be filled with '999', eSecondTerm will be filled with '998'.

If an ini file of the name given is found, but no key 'Terms' or no section (property) 'FirstTerm' is found, then eFirstTerm.text will be filled with '999'.

These things had to be done in the OnFormActivate handler (which always runs as an app fires up, just after the code in the OnFormCreate) because until the FormActivate code finishes, the edit boxes (eFirstTerm and eSecondTerm) don't exist.

====
At this point, you might as well remove the...

eFirstTerm.text:='0';
eSecondTerm.text:='0';
laSum.caption:='0';
laProduct.caption:='0';

... lines from the ForMCreate procedure. (The less code there is in your app, the fewer the laces for bugs to exist, the fewer the distractions from How It Works.

====
Back to the main issues...

So far, we have built all that is necessary to get the app started with whatever example terms you want the user to see when the app launches.

As our little app is pretty pointless, it hardly matters what is in eFirstTerm, eSecondTerm, but just think about the possibilities... suppose you have an app for calculating bills in a restaurant... and that tax must be added. A way to "set" the tax rate outside the code of the app would be useful, wouldn't it?

The [AppWindow] section

The [AppWindow] section is more or less "more of the same". But this time we are dealing with integers, not strings.

Before we use the [AppWindow] keys, I will point something out that you may or may not know...

If you are a Lazarus beginner, you will soon get used to your app starting in whatever state the form is in during the programming process. I.e. if you have the form "minimized", i.e. using part of the screen, and 150 down from the top of your screen, 500 from the left, 200 high and 300 wide, then that's where the form will be when you run the code.

But! Put the following into you FormCreate, and run the app again.

left:=30;
top:=10;
width:=350;
height:=120;

Now when you run the app, it will probably pop up in that place, with those dimensions.

(Digression: I say "probably", because I ignoring the possibility that something will put the window into the "maximized" or "minimized" states. I don't think that will arise. If it does, add a OnFormShow handler, make it execute "application.restore;", "Restore" being Microsoft's name for the window state between Minimized and Maximized. That may introduce other problems though! Sigh, Tread those paths at your peril! Check out Google hits on "lazarus application.minimize" if you feel you must. Or hits on "lazarus TWindowState. Digression ends.)

Now we will move the positioning and sizing of the window to the ini file.

Remove the...

left:=30;
top:=10;
width:=350;
height:=120;

... you currently have in your OnFormCreate handler.

Modify your FormActivate...

procedure Tldn150F1.FormActivate(Sender: TObject);
var dfIniFile:TIniFile;//"df" in name from "datafile". Could be anything.
begin
  //Heart of reading from ini file
  dfIniFile:=TIniFile.Create('ldn150ini.txt');

  with dfIniFile do begin
     eFirstTerm.text:=readstring('Terms','FirstTerm','999');
     eSecondTerm.text:=readstring('Terms','SecondTerm','998');
     with ldn150F1 do begin
       left:=readinteger('AppWindow','left',30);
       top:=readinteger('AppWindow','top',10);
       width:=readinteger('AppWindow','width',350);
       height:=readinteger('AppWindow','height',120);
       end;//with ldn150F1
     end;//with dfInifile...

  dfIniFile.Free;

  //End of "Heart of...."

  DoIt;

end;//FromActivate

And now your app should open where and how the .ini file says it should.

You have to THINK

As we go further towards the final version, it becomes more and more important to think carefully about what you do where, and when.

Initially, you created your ini file with a text editor. Fine. No problem.

As we get fancier, you're going to have to be careful to remember when the app looks at... and (later) when it CHANGES what is in the ini file.

You can leave your text editor open, with your .ini file loaded. But remember: You need to SAVE any changes you want to make to the .ini file to the disk before you can expect the provisions inside the app for being influenced by the ini file to take effect. And when, soon, we get to having the app change what is in the copy of the .ini file on your disk, the whole thing needs even more care and thought!

So far, so good?

I hope what we have done so far is clear... even if it may feel a bit useless?

Though it may seem "useless", it is not entirely without use. What we have so far is fine for things like the tax rate issue. You just have to edit the .ini file by hand, with a text editor, and "it works".

But .ini files become really useful when they can save things you've set while using the program... for instance, where the window is on the screen. We've got half of that done already.

Next, we are going to create a crude "answer" to saving settings, so that your app opens as you left it, the next time you use it.

Later we will extend the crude answer.

Save State

Add a button to your form. Call it buSaveState. Have that call a procedure called SaveState. Create the procedure to do the following...

procedure Tldn150F1.SaveState;
var dfIniFile:TIniFile;//"df" in name from "datafile". Could be anything.
begin
   //Heart of writing to ini file
  dfIniFile:=TIniFile.Create('ldn150ini.txt');

  with dfIniFile do begin
     writestring('Terms','FirstTerm',eFirstTerm.text);
     writestring('Terms','SecondTerm',eSecondTerm.text);
     with ldn150F1 do begin
       writeinteger('AppWindow','left',left);
       writeinteger('AppWindow','top',top);
       writeinteger('AppWindow','width',width);
       writeinteger('AppWindow','height',height);
       end;//with ldn150F1
     end;//with dfInifile

  dfIniFile.Free;

  //End of "Heart of...."

  DoIt;

end; //SaveState

... and test that as follows...

When it re-opened, was it the size and position as just before you shut it? Were the two numbers as they were?

By the way: If there had been no ldn150ini.txt file when you ran SaveState, one would have been created.

All of that should be thus! If it isn't, look closely at your code!

=====
N.B.: You don't have to incorporate a "SaveState" procedure to use .ini files. But you will lose some of the greatest benefits they can deliver if you don't.

The tricky bit... but vital...

Last but not least... if you want to have a SaveState procedure as part of how you use the app's ini file, then you probably want the "SaveState" called as the app shuts down.

This isn't too hard to accomplish... but it is easy to do things that defeat your intentions.

First Gotcha: For years, I've put "Quit" buttons on my apps. They invoked application.terminate.

That method of shutting down an app will bypass the "make it do SaveState when closing" system described below.

If you have a "Quit" button, instead of using application.terminate, use "close".

I've added a Quit button to the sample code to illustrate this.

Make he form's OnClose event handler...

procedure Tldn150F1.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  SaveState;
end;

Second Gotcha: While you are developing the app in the IDE, you may be in the habit of clicking the red square in the toolbar of the IDE's main window to shut down an instance of the app running under the IDE.

This too invokes an "application.terminate", and so, again, you will not get the SaveState during the shutting down of the app.

If you are in the habit of creating buttons to close down an app, it might be wise to put a note in the sourcecode within the button's Click event handler..

//DO NOT USE APPLICATION.TERMINATE here.
//If you do, the usual "write to ini file"
//  WILL NOT HAPPEN when you end the app by this button.

===
More on the same general subject: If you click the "x" in the upper right hand corner of the app's window that does do a "close" (what you want), not the "terminate", which bypasses the OnClose event handler.

If you are working on what you ini file is supposed to do, and it doesn't seem to be doing it, be sure you are quitting the app "correctly", i.e. in the way that the ini file system needs, if you want your ini file updated as you exit the app. If you are making edits to an .ini file as you test tweaks to your code, be sure that you are saving edits you've made in your text editor. Also be careful that you don't exit an instance of the running app after making a change to the ini file... exiting the app may "edit the edited" ini file, might it not?

There are a couple of ways to go wrong, traps that are easy to fall into. But if you think, you should soon routinely steer clear of them.

The problems you will have if you forget the advice above can be extremely exasperating... and extremely difficult to figure out until you remember that what's in the ini file can be dynamic. And the app uses what is in the ini file, at the moment it consults it. What you think is in the ini file plays no part in what the app does. Boring, but true.

======
A detail... I've repeatedly spoken of "the ini file". There's nothing to stop you from having more than one. You just have to give each one a different name.

You might have one with things that rarely change... registration codes to "switch on" features in the app? A bit of text which appears to indicate what circumstances this instance of the app is configured for? And another for more transient things, like window size and position?

You can even "daisy chain" ini files... where the first one called includes the name of a second one. With such a scheme, the second ones could come from a set of similar files (different names), with each containing the settings for a particular use of the app.

The system's main limit is your imagination. If you can imagine it, there's probably a way to do it.

=====
A final detail...

The code above Will Work.

But there's a dangerous element.

It all depends on an ini file.

The name of the file is important, and it appears in two.

That makes it very easy for the two places where the file is named to get out of step. Which would be disastrous to the operation of the app.

So, in the final version of the code, note the "const skIniFileName='IniFileDemoIni.txt';" near the top of the code. Note that each time we want to use TIniFile.Create, we say "TIniFile.Create(skIniFileName)"

That way, not only is it easier to change the name of the ini file we are using, but the name "automatically" changes in every place it needs to.

Full listing, final code...

You can just download a .zip file with the whole Lazarus project in it, along with the .exe that it would build... but I advise you to "build it yourself", following the tutorial. Then you will see the various elements, as they are put in. But, for reference...

unit ldn150u1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, IniFiles;

const skIniFileName='IniFileDemoIni.txt';
      skVer='25 Jun 22';
      //"sk": String "Konstant" (constant)

type

  { Tldn150f1 }

  Tldn150f1 = class(TForm)
    buSaveState: TButton;
    buQuit: TButton;
    eFirstTerm: TEdit;
    eSecondTerm: TEdit;
    laSum: TLabel;
    laProduct: TLabel;
    procedure buQuitClick(Sender: TObject);
    procedure buSaveStateClick(Sender: TObject);
    procedure eFirstTermChange(Sender: TObject);
    procedure eSecondTermChange(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
    procedure DoIt;
    procedure SaveState;
  public

  end;

var
  ldn150f1: Tldn150f1;

implementation

{$R *.lfm}

{ Tldn150f1 }

procedure Tldn150f1.FormCreate(Sender: TObject);
begin
  //The next two are not critical to the app's ini file elements.
  ldn150f1.caption:='LDN150- ini file demonstrator. Version '+skVer;
  application.title:='LDN150 Ini Demo';
end;

procedure Tldn150f1.FormActivate(Sender: TObject);
var dfIniFile:TIniFile;//"df" in name from "datafile". Could be anything.
begin
  //Heart of reading from ini file
  dfIniFile:=TIniFile.Create(skIniFileName);

  with dfIniFile do begin
     eFirstTerm.text:=readstring('Terms','FirstTerm','999');
     eSecondTerm.text:=readstring('Terms','SecondTerm','998');
     with ldn150f1 do begin
       left:=readinteger('AppWindow','left',30);
       top:=readinteger('AppWindow','top',10);
       width:=readinteger('AppWindow','width',350);
       height:=readinteger('AppWindow','height',120);
       end;//with ldn150f1
     end;//with dfInifile...

  dfIniFile.Free;

  //End of "Heart of...."

  DoIt;

end;//FromActivate

procedure Tldn150f1.DoIt;
begin
  laSum.caption:=inttostr(strtoint(eFirstTerm.text) + strtoint(eSecondTerm.text));
  laProduct.caption:=inttostr(strtoint(eFirstTerm.text) * strtoint(eSecondTerm.text));
end;//DoIt

procedure Tldn150f1.SaveState;
var dfIniFile:TIniFile;//"df" in name from "datafile". Could be anything.
begin
   //Heart of writing to ini file
  dfIniFile:=TIniFile.Create(skIniFileName);

  with dfIniFile do begin
     writestring('Terms','FirstTerm',eFirstTerm.text);
     writestring('Terms','SecondTerm',eSecondTerm.text);
     with ldn150f1 do begin
       writeinteger('AppWindow','left',left);
       writeinteger('AppWindow','top',top);
       writeinteger('AppWindow','width',width);
       writeinteger('AppWindow','height',height);
       end;//with ldn150f1
     end;//with dfInifile

  dfIniFile.Free;

  //End of "Heart of...."

  //There's a "DoIt;" in the machine readable version of this
  //  which is available for download. It shouldn't be here.

end; //SaveState

procedure Tldn150f1.eFirstTermChange(Sender: TObject);
begin
  DoIt;
end;

procedure Tldn150f1.buSaveStateClick(Sender: TObject);
begin
  SaveState;
end;

procedure Tldn150f1.buQuitClick(Sender: TObject);
begin
  close;
end;

procedure Tldn150f1.eSecondTermChange(Sender: TObject);
begin
  DoIt;
end;

procedure Tldn150f1.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  SaveState;
end;

end.

A few words from the sponsors...

Please get in touch if you discover flaws in this page. Please mention the page's URL. (wywtk.com/lut/ldn150inifiles/ldn150.htm).

If you found this of interest, please mention in forums, give it a Facebook "like", Google "Plus", or whatever. If you want more of this stuff, help!? There's not much point in me writing these things, if no one feels they are of any use.



index sitemap
What's New at the Site Advanced search
Search tool (free) provided by FreeFind... whom I've used since 2002. Happy with it, obviously!

Unlike the clever Google search engine, this one merely looks for the words you type, so....
*    Spell them properly.
*    Don't bother with "How do I get rich?" That will merely return pages with "how", "do", "I"....

Please also note that I have three other sites, and that this search will not include them. They have their own search buttons.

My SheepdogSoftware.co.uk site, where you'll find my main homepage. It has links for other areas, such as education, programming, investing.

My SheepdogGuides.com site.

My site at Arunet.




How to email or write this page's editor, Tom Boyd. Please cite page's URL, ldn150.htm, if you write.


Test for valid HTML

Page has been tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. It passes in some important ways, but still needs work to fully meet HTML 5 expectations. (If your browser hides your history, you may have to put the page's URL into the validator by hand. Check what page the validator looked at before becoming alarmed by a "not found" or "wrong doctype".)

AND passes... Test for valid CSS


Why does this page cause a script to run? Because of the Google panels, and the code for the search button. Also, I have my web-traffic monitored for me by eXTReMe tracker. They offer a free tracker. If you want to try one, check out their site. Why do I mention the script? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....