Ini Ins and Outs
Exploiting your app’s ini file(s) with INI.CC
by David Stone

| Introduction dBASE’s use of ini files Dependencies A general strategy for managing ini files Ini file and INI.CC Basics INI Functions INI Properties Creating an INI file Initialize the _app properties Role of the _app Properties Test for existence of the ini file Instantiate an INI object Set Entries and Values Establish the rest of the Entries Extract information from the ini file and store it in _app properties A relevant property that we’re not using here — oMyIni.default Multiple comma-separated Values Update the form and controls to reflect preferences Daily Tips Provide a means to specify preferences and update _app properties and controls Change the entryfield colors Change form coordinates The Preferences Dialog Box Specify the main form’s color Specify the DEO folder Accept or cancel Store preferences in the ini file on program exit Conclusion

THIS article will introduce the reader to ini file management by providing and explaining a working example of the use of `INI.CC`, a neat little custom class created by Keith Chuvala, Bowen Moursund, and Kenneth Chan.`INI.CC` makes use of the three Windows API (Application Programming Interface) functions commonly used to manage ini files:`GetPrivateProfileString()`,`WritePrivateProfileString()`, and `GetPrivateProfileSection()`, and relieves us of having to know about or manage these API functions. Instead,`INI.CC` gives us several normal-looking functions that make management of ini files a much simpler matter.

Quick-Start: If you find line-by-line instructions tedious, you may wish to just run the included form to see how it and its two sub-forms behave, then examine the code, which is pretty extensively commented. If there’s anything you don’t understand, come back here to read about it.
Ini Introduction

Ini (initialization) files have been around since Windows 3.1. They are standard ASCII text files that contain data organized into categories called Sections. Their role, like that originally provided by the DOS files `config.sys` and `autoexec.bat`, is to “remember” configuration information used by both the system (`win.ini`,`system.ini`, etc.) and by individual applications (e.g., CorelDRAW’s `coreldrw.ini`). The information in an application’s ini file is mainly comprised of user-specifiable program settings, from recent files to onscreen window coordinates to the location of app-related data files.

Win95, a more complex system than Win3.1, introduced the Registry. It was intended to be a single central repository of settings for the operating system, hardware, and user applications as well, thus providing an alternative to the welter of app-related ini files on a hard drive. The Registry differs from all prior configuration files in that it is a binary database file rather than a simple text file, and is thus less directly accessible to users intent on mischief (e.g., finding a password). Furthermore, since it is hidden in the system folder, it is far less likely to be accidentally deleted. Although the Registry is extensively used by the Windows OS and is well-used by many applications, ini files are still with us. In fact, even `win.ini` is still with us and is regularly consulted and updated by the Windows OS. If you have any doubts about the continuing use of ini files, even in a clean-installed Win2K system, do a search for`*.ini` and watch the ListView control fill up.

On the other hand, here’s what Microsoft has to say about the use of their `GetPrivateProfileString()`: “This function is provided for compatibility with 16-bit Windows-based applications. Win32-based applications should store initialization information in the registry.”

dBASE’s use of ini files

In addition to its relatively few entries in the Registry, dBASE relies heavily on ini files. The dBASE executable itself uses `DB2K.INI` in preference to the Registry to store the wide variety of user preferences, and any application that you compile to an exe file in dBASE expects to have its own ini file, named after the application’s exe file. So if your exe is called `MyBigApp.exe`, it will, sooner or later, have an ini file called `MyBigApp.ini`.

dBASE will generate an ini file for your compiled application if there isn’t one: If you do not provide the ini file, the dBASE runtime will create one when the exe file is first run, just after the form opens. It is possible to deduce the Moment of Creation by placing “ini file detection code” to display a message in the command window such as

| `? "Now in header section... is ini file present yet? " +file("Myini.ini")`

in the header section and in a custom `Open()` function, which always runs before the form is opened. The ini file is thus detectable in the form’s `onOpen()` event (which follows the opening of the form), but not prior to it.

What is in the ini file of a compiled dBASE app? Below is an auto-generated ini file for a compiled dBASE application:

| `[Toolbars]` `Standard=0` `StandardFloat=0` `StandardStyle=0` `[Desktop]` `Maximized=1` `Position=64 102 1216 921` `StatusBar=0` `[CommandWindow]` `Open=1` `Maximized=0` `Minimized=1` `[Directories]` `0=V:\MenBase` `Current=0` `[Database]` `Name=`

Other than the location of the default folder under`[Directories]`, there is nothing specific to this application in the auto-generated ini file. And there need never be, if you do not wish to use the ini file any further than this. However, the point of this article is to show you how to make your application more useful to the user by letting it “remember” user preferences, and restore them each time the program loads.
Dependencies

You will, of course, need a copy of `INI.CC` and its two support files. These three files are part of the `dUFLP` (dBASE Users’ Function Library Project) package. The version of `INI.CC` you have will depend on which version of the `dUFLP` you have. If you have an older version of the `dUFLP`, the files required are `INI.CC`, `STRINGEX.CC` and `dUFLP.H`. If you have either of the last couple of versions of the `dUFLP`, the files are `INI.CC`, `STRINGEX.CC` and `SETPROC.PRG`. If you do not have the `dUFLP` package, it is available at dbase.com . However, a set of the three ini-related files from the `dUFLP` is also included with the zipped demo forms via a link near the top of this article. Although the newest version of these files is set up to use Source Aliasing, I prefer to simply place a copy of these three files in the folder of the application I am working on, as this makes it easier to get them into the app’s Project file when it’s time to compile and build the exe. Keep in mind that each of the sets of 3 files is a “matched” set, so do not use the `INI.CC` from one set and the `SETPROC.PRG` from another; the `INI.CC` in each set expects to find the correct versions of its dependency files. Although the newer `INI.CC` file suggests that you must have `dUFLP` Source Aliasing set up in order to use the ini-related files, it is not necessary — just have a copy of each of the three files in your working folder and all will be well.

A general strategy for managing ini files

Since an ini file’s role is to store information and recall it, decisions must be made as to when, in the running of an app, these processes should occur, how the information should be stored while the app is running, and at what point new/changed preference data should be saved to the ini file. Programmers are likely to vary their approaches according to the amount and type of data that will be managed by the ini file, but here’s a basic approach that will do the job:

In the header section of the form:
1. Initialize app properties, check for the existence of the ini file, generate it if needed and establish default values
2. Extract values from the ini file and store them in app properties

In a custom Open( ) function of the form:
3. Update the form and controls to reflect preferences

In the open form:
4. Provide a means to specify preferences and update app properties and controls
5. Store preferences in the ini file on program exit

A main form, two sub-forms and an ini file accompany this article and are used in the examples below.
Ini file and INI.CC Basics

Before we proceed with examples of the use of `INI.CC`, let’s look at the relevant ini file terminology, and also take a quick look at `INI.CC`’s functions to see how they interact with ini file components.

Below is a Section of an ini file, grabbed from dBASE’s “auto-generated” ini file shown above:

`[Toolbars]` is referred to in ini-speak as a Section. A Section must be set in square brackets. `Standard` or `StandardFloat` or `StandardStyle` is referred to as an Entry. Each of the zeroes is referred to as a Value. An Entry can have more than one Value, usually comma-separated.

| Ini files only store “character” data. You must always convert numeric and logical values to character strings before storing them in an ini file. |

That’s all there is to ini terminology. I will always capitalize the first letter of these terms in this article since the words are often used in other contexts, and I want it to be clear when I mean the “ini” versions. Now let’s briefly look at the `INI` class.

Once an `INI` object is created (instantiated) with a name such as `oMyINI`, several ini-related functions (also called “methods”) become available. My guess is that after glancing at the above terminology and recalling the nature of the job at hand, you won’t have much trouble figuring out what most of these functions do:

Functions | Description
SetValue(cSection,cEntry,cValue) | To create, as needed, the Section and Entry, then to set the specified Value
GetValue(cSection,cEntry) | To retrieve the Value from the specified Section/Entry
GetSection(cSection) | To retrieve all the Section’s Entries and their Values into an array, with its object reference contained in oMyIni.SectionArray
SectionArrayParse2Arrays([cDelimiter])* | To parse the array returned by GetSection(); see INI.CC for instructions
DeleteSection(cSection)* | To delete the specified Section and all its Entries/Value(s)
DeleteEntry(cSection,cEntry)* | To delete the specified Entry and its Value(s)
Flush() | To force the ini file to be written to disk
SetIniFile(cIniFile)* | To create a new ini file of the specified name; this can also be accomplished when instantiating the INI object
Release() | To release the INI object

* = not used in the example in this article

Properties | Description
IniFile | The filename of the INI file
Section | The Section most recently read or written-to
Entry | The Entry most recently read or written-to
Default* | A programmer-provided default value for the GetValue() method, normally left blank (i.e., Value="")
SectionArray* | The last-read one-dimension array of Entries of a Section in this form: Entry=Value
EntriesArray* | The last-parsed Entries from SectionArray
ValuesArray* | The last-parsed Values from SectionArray

* = not used in the example in this article

The form called `DemoIni.wfm` supplied with this article allows several kinds of user preferences to be stored in the INI file and then retrieved. Below is a screen shot of the main form and its modal sub-form opened by the Set Preferences button:

The main form will save the following information as Values under the`[Preferences]` Section of its ini file:

1. The `colorNormal` property of the Change my colors entryfield.
2. The position and size of the form on the screen.
3. Within the modal sub-form (Preferences): the main form’s `colorNormal`, the location of the DEO folder, and whether to display a “Daily Tip”.

Only #3 will ask the user to “OK” any changed settings; the others will be saved automatically to the ini file on exiting the program if any one has been changed.

| “DEO” refers to a new feature of dBASE called Dynamic External Objects. Briefly, this feature provides the option to maintain an application as a set of small object (`.wfo`, `.mno`,`.reo`,`.dmo`, etc.) files rather than as a single large `EXE` file. The advantage to DEO is that a modification to a program component requires that only the single changed component be re-compiled and sent to users. You are thus saved the time needed to regenerate a large EXE file, and the upload time is short — an object file is usually very small. |

Creating an INI file
`[Top|General Strategy]`

The form created for this article has the first portion of ini-related code in its header section.

| The header section, as shown in the figure below, is the region above the line that says
| `** END HEADER -- do not remove this line`
Any code placed in this region will be run before the form object is created (instantiated). Placement of the ini-related code here is a matter of choice. Instead, it could be associated with the form’s `onOpen()` event (although it might cause blinking or visible movement of the form), so a better choice would be to place it in a custom Open( ) function or in a custom `Init( )` function. I am placing it in the header section mainly out of habit. |

Below is a screen shot of the Source Code Editor showing the header section.

Initialize the _app properties

We will use custom`_app` properties to store data extracted from the ini file.

| `_app` represents the object which is the currently running instance of dBASE. So, like any object, you can tack onto it, dot-style, any custom properties you’d like, as long as you steer clear of the built-in properties (see “`_app`” in OLH for a list of property names to avoid). I am using`_app` properties here because they are visible to the entire application, and are therefore suited to holding information which needs to be seen by the entire application. You may have read scornful comments about the use of`_app` properties, since they are functionally very similar to the much-maligned and spurned public variables of the DOS dBASE versions. My advice: ignore those comments and use`_app` properties where they are useful. |

Below is the beginning of `DemoIni.wfm`’s header section where it initializes the custom`_app` properties:

| `// Init the _app properties to be used to store ini data` `*----------------------` `// Entryfield colors` `_app.EF1color = ""` `*----------------------` `// Form color` `_app.formColor = ""` `*----------------------` `// DEO folder location` `_app.deoLoc = ""` `*----------------------` `// Display daily tip` `_app.dailytip = ""` `*----------------------` `// Form size, position on screen` `_app.fheight = ""` `_app.fleft = ""` `_app.ftop = ""` `_app.fwidth = ""`

As you can see, an`_app` property has been initialized for an entryfield `colorNormal` property, another for the main form’s `ColorNormal` property, another for the location of the DEO folder, another for the Daily Tip option, and then four more to hold size/position coordinates for the main form. Although you could initialize these properties at the time they are loaded with ini data, I prefer having a nice orderly list at the top to remind me what I'm working with.
Role of the _app properties

The role of these properties is, first, to hold their corresponding data when it is extracted from the ini file. They will then be used to modify the corresponding form and object properties before the form opens. If the user changes any of the values while using the program, the`_app` properties will be updated so they can be used to write the new values back to the ini file when the user exits the program.

| Use of`_app` properties, or any variables at all, to manage ini data is not a necessity — it’s entirely possible to achieve the same functionality without them. However, to do without them, you would have to place your initial ini code in a custom `Open()` or `Init()` function because you would need access to the properties of the instantiated form and its objects, since it is these properties that you will be setting. Furthermore, you would have to do this for any form in the app that has ini-settable properties. More on this below. |

Test for existence of the ini file

The next chunk of code is where the action starts:

| `// The ini file and its full path...` `cInihome = set("directory")+"\DemoIni.ini"` `*----------------------` `// if we can't find ini-file...` `If not file(cInihome)` `msgbox("DemoIni.ini file is missing...so, we'll make one.","FYI",64)` `...`

We first need to test for the existence of the ini file so we’ll know which direction we have to take: either create it if it doesn’t exist, or extract information from it if it already exists. Even though it’s safe to assume when running an app that the ini file will be there, we need to provide for either scenario.

As you can see in the above code, we are a establishing a variable called `cInihome` and assembling contents to be placed in it. The dBL function `set("Directory")` will return the current drive and path, e.g.,`c:\dBASE\IniFileApp`, and we use the “+” sign to add (concatenate) the necessary backslash, followed by the name of our ini file, to the drive:\path string.`cIniHome` should then contain a string of characters that looks something like this:

| `c:\dBASE\IniFileApp\DemoIni.ini`

| If you don’t include the full path to the ini file, it will not be automatically placed in the current folder. Instead, if the ini creation code is run from a`.wfm` file in the IDE, the ini file will be placed in the folder where `dB2K.exe` is located; or if run from an `.exe` file, it will be placed in the folder where `dB2KRun.exe` is installed. However, if you have no ini file and then allow it to be created automatically when your compiled app is run, the newly created ini file will be placed in the same folder as your exe file. |

We then start an `If/Else/Endif` to test for the existence of the ini file. The first section of the `If` structure assumes we cannot find it, and we thus begin the code to create a new ini file. Incidentally, the whole process will be encased within a nice safe `Try/EndTry` in case anything goes terribly, tragically wrong.
Instantiate an INI object

We start by using `set procedure to` to tell dBASE to open the `INI.CC` file, and we then instantiate an object of the `INI` class:

| `TRY` `set proc to ini.cc additive` `// create new INI object` `oMyIni = new Ini(cInihome) // Instantiation` `...`

The last line above instantiates the `INI` object into a variable called `oMyIni`. Notice that we are sending the name of the ini file (which is contained in the variable `cInihome`) as a parameter. The `oMyIni` object will look for this file, and if it isn’t there, will create it for us. So, after that line of code has been processed, a new empty ini file of the specified name will exist. We now need to add a Section and some Entries and corresponding Values.
Set Entries and Values

In the code below, we use the `oMyIni` function called `SetValue()`for the first time. It uses three parameters to specify the Section, Entry, and Value:

- `cSection`, which specifies the name of the Section (we’ll call it`[Preferences]`) under which the Entry and its associated Value will be placed. The Section will be created if it doesn’t exist.
- `cEntry`, which specifies the name of the Entry. If it doesn’t exist, it will be created.
- `cValue`, which specifies the Value of the Entry. The Value will replace either a blank or an existing Value.

This function must be used once for each Entry or Value you wish to establish. Since we’re on the “ini-file-doesn’t-exist” branch of our `If/Else/Endif`, we know that the function is going to be creating the Section as well each Entry and Value.

| `...` `// Use oMyIni.SetValue(cSection,cEntry,cValue)` `// to set values of the entryfield color` `oMyIni.SetValue("Preferences","EF1color","0xffb3ff/0xcc9999")` `// update _app property` `_app.EF1color := "0xffb3ff/0xcc9999"` `...`

This first time the `SetValue()` function is used,`oMyIni` will notice that there is no`[Preferences]` Section, and will create it. It will then create an Entry called `EF1color`, add an`=` sign after it, then concatenate to that the Value, which is a `colorNormal` value of `0xffb3ff/0xcc9999`

The last line in the code above also assigns the same `colorNormal` value to the corresponding`_app` property. Recall that this`_app` property is being used to transmit the corresponding `ini` Value to the form.

The result will be the following two lines written to the ini file:

| `[Preferences]` `EF1color=0xffb3ff/0xcc9999`

Establish the rest of the Entries

We now reiterate the `SetValue()` function to establish the rest of the Entries/Values that we wish to include in the ini file, and we also set their corresponding`_app` properties for later use by the form:

| `...` `// Form color` `MyIni.SetValue("Preferences","formColor","BtnFace")` `// update _app property` `_app.formColor = "BtnFace"` `*********************************` `// DEO folder location` `oMyIni.SetValue("Preferences","deoLoc",set("directory"))` `// update _app property` `_app.deoLoc = set("directory")` `*********************************` `// Daily tip` `oMyIni.SetValue("Preferences","dailytip","true")` `// update _app property` `_app.dailytip := "true"` `*********************************` `// Form position/size coordinates (height,left,top,width)` `oMyIni.SetValue("Preferences","fcoords","10.6,22,6,58.3") // note parentheses around the string of values...` `// update _app property` `_app.fheight = "10.6"` `_app.fleft = "22"` `_app.ftop = "2"` `_app.fwidth = "58.3"` `...`

You may have noticed, above, that the last Entry,`fcoords`, is composed of four comma-separated Values. Also notice that we are storing the individual values in their`_app` properties as character strings rather than as numeric values, e.g., `_app.fheight = "10.6"`. This is an arbitrary decision. These four values must, at some point, be converted to numeric values since the form’s size/position properties are numeric. I have chosen to keep all the`_app` properties as “character” to match the all-character ini Values, and to convert to numeric only as needed when setting object properties. You may choose to do it differently, of course.

We then use `oMyIni.Flush()` to force the ini file to be written to the disk and we also release the `oMyIni` object. The rest of the `Try/EndTry` code is shown here as well:

| `...` `// force a write of the new ini file to disk` `oMyIni.Flush()` `// release INI object` `oMyIni.Release()` `CATCH (Exception e)` `msgbox("Unable to create INI file","INI file creation error",16)` `ENDTRY` `...`

Extract information from the ini file and store it in _app properties
`[Top|General Strategy]`

If the ini file already exists

Still in the header section of the form, we now enter the `Else` branch of our `If/Else/Endif` structure, which covers the alternative scenario — the ini file is already present. Here, we will use the `GetValue(cSection,cEntry)` function to retrieve each Value and store it in its respective `oMyIni.Value` property. If `GetValue()` cannot find either the Section or the Entry, or if the Value is blank, the `oMyIni.Value` will be blank, and we will use `SetValue()` to create what was missing.
A relevant property that we’re not using here — oMyIni.Default

`INI.CC` makes use of a “default Value” feature of the `GetValue()` function: if you load the property called `oMyIni.Default` with the desired default contents of the Entry’s Value, then call the `GetValue()` function, if `GetValue()` cannot find either the Section or the Entry, it will create them as needed, and will return the contents of the `Default` property to the `oMyIni.Value` property. However, it will not set the ini file Value with your specified default value. Instead, the Value in the ini file will be left blank. Moreover, if the Section and Entry are already present in the ini file but the existing Value is blank (i.e., un-set), the function assumes that the blank value is valid, and so the contents of `oMyIni.Default` will not be returned to the `oMyIni.Value` property; instead, a blank is returned. That’s OK if the desired default is a blank, but it’s not OK if you wish the default to be a string or numeric value.

So, you will have to decide whether to use the `oMyIni.Default` property or not. For this article’s form, I do not use it at all. Instead, I just test for the `GetValue()`’s return value (in the `oMyIni.Value` property), and if it is blank (meaning either no Section, no Entry, or a blank Value), I use `SetValue()` to create what’s missing and to set the proper Value. The result is this: no blank values in any of the ini file Entries. Since you may desire a different outcome in your application, you can tailor your use of the `GetValue()` function as needed.

The `GetValue()` code is shown below for all but the last ini Entry:

| ... `// ini-file exists and we need to extract Values from it` `Else` `set proc to ini.cc additive` `oMyIni = new Ini(set("directory")+"\DemoIni.ini") // Instantiation` `// Check Entries — if any is missing, empty or wrong, fix it. Otherwise, get the Value.` `*----------------------------` `oMyIni.GetValue("Preferences","EF1color") // entryfield colorNormal` `if empty(oMyIni.value) // no Value set, so set it` `oMyIni.SetValue("Preferences","EF1color","0xffb3ff/0xcc9999")` `_app.EF1color = "0xffb3ff/0xcc9999"` `else // otherwise, get the Value into the _app property` `_app.EF1color = oMyIni.value` `endif` `*----------------------------` `oMyIni.GetValue("Preferences","formColor") // Form color` `if empty(oMyIni.value) // no value set, so set it` `oMyIni.SetValue("Preferences","formColor","BtnFace")` `_app.formColor = "BtnFace"` `else // otherwise, get the value into the _app property` `_app.formColor = oMyIni.value` `endif` `*----------------------------` `oMyIni.GetValue("Preferences","deoLoc") // DEO folder` `if empty(oMyIni.value) // no value set, so set it` `oMyIni.SetValue("Preferences","deoLoc",set("directory"))` `_app.deoLoc = set("directory")` `else // otherwise, get the value into the _app property` `_app.deoLoc = oMyIni.value` `endif` `*----------------------------` `oMyIni.GetValue("Preferences","dailytip") // Display daily tip` `if empty(oMyIni.value) // no value set, so set it` `oMyIni.SetValue("Preferences","dailytip","true")` `_app.dailytip = "true"` `else // otherwise, get the value into the _app property` `_app.dailytip = oMyIni.value` `endif` `...`

Multiple comma-separated Values

For the sake of illustration, I have used multiple Values for the last Entry (called `fcoords`). These four Values will specify the form’s size and location on the screen (`form.height`,`form.left`,`form.top`,`form.width`). Although we could have set four separate ini Entries for these four coordinates, it’s more efficient and also pretty easy to stick them all in the same Entry. However, on retrieving them, we will parse them out to four separate`_app` properties — not absolutely necessary, but easier to deal with in the form, I think.

`GetValue()` is used in the same way as above. The only difference here is that if a Value is present we assume it is a comma-separated string of four values and then use the `Tokenize()` function to place the values in an array called `aCoords`.

| `Tokenize()` is a very nice little function that I borrowed from Vic McClung’s Printer Class. Its job is to accept a character-separated (usually comma-separated) sequence of character strings and parse the strings into an array. It takes two parameters: the string to be parsed, and the separation character. Here, our list of form coordinates is comma-separated. The `Tokenize()` function is included within the `DemoIni.wfm` file beneath the Form class so that it can be “seen” by code in the header section (remember that while we’re in the header section, the form has not yet been instantiated, so its methods are not yet available). Thus `Tokenize()` is not a method of the form, and so it can be called directly, i.e.,`Tokenize()` rather than `form.Tokenize()`. This is shown below along with some `TRY/CATCH` error checking in case the coordinates have been fooled with — we want to avoid seeing an “invalid subscript reference” error. “Invalid subscript reference” is a common error which means the program has asked for a non-existent array element. For example, if the code asks for `aMyArray[4]` and there are only 3 elements in the array, this error will occur. Unfortunately, it’s not always easy to figure out which array is causing the problem. |

| `(...)` `*----------------------------` `oMyIni.GetValue("Preferences","fcoords") // Form size & position` `// no Value for this Entry` `if empty(oMyIni.value)` `// fix the Entry with defaults` `oMyIni.SetValue("Preferences","fcoords","10.6,22,6,58.3")` `// set default _app properties` `_app.fheight = "10.6"` `_app.fleft = "22"` `_app.ftop = "6"` `_app.fwidth = "58.3"` `// there is a value...` `else // use tokenize function to parse the string` `aCoords = tokenize(oMyIni.value,",")` `TRY // in case someone has fooled with the coord values` `// and we have fewer than 4 values...` `_app.fheight = aCoords[1]` `_app.fleft = aCoords[2]` `_app.ftop = aCoords[3]` `_app.fwidth = aCoords[4]` `CATCH (Exception e)` `// there was at least 1 missing coordinate...so set default values` `oMyIni.SetValue("Preferences","fcoords","10.6,22,6,58.3")` `_app.fheight = "10.6"` `_app.fleft = "22"` `_app.ftop = "6"` `_app.fwidth = "58.3"` `endif` `*----------------------------`

| I’m sure you can see that there are many opportunities for bad ini data to sabotage our process here (such as nutty coordinates that put the form ½ mile off-screen)… so you will have to decide how much error-checking code you wish to write. And, considering that it is mostly, if not all, in defense against users’ meddling with the ini file, you can now see why the Registry (which is relatively inaccessible) seemed like such a good idea to Microsoft. In fact, we could just as easily perform this entire process using the Registry and `REGISTRY.PRG` instead of using the ini file and `INI.CC`. Perhaps it will be the subject of a future dBulletin article. |

To wrap up this “header section” aspect of ini file management, we dump the `oMyIni` object.

| `// release and null the oMyIni object` `oMyIni.Release()` `oMyIni = null` `Endif`

Here is what a brand-new ini file will now look like

Let’s assume the worst — that we had to re-create the ini file from scratch. It will now look like this:

| `[Preferences]` `EF1color=0xffb3ff/0xcc9999` `formColor=BtnFace` `deoLoc=X:\DemoIni <your own current folder will show up here...>` `dailytip=true` `fcoords=10.59,22.00,2.00,58.29`

Where’s the rest of the ini innards??

You may be thinking “Hey, where is the rest of the ini file contents?? All the stuff that dBASE puts in automatically?” Not to worry — when your app is compiled and run as an exe, those extra Sections/Entries will be added automatically. They are not added when the form is run in the IDE.

Ready to instantiate the form

Since we now have an ini file and have filled our`_app` properties with data, we are ready to instantiate the form. We’ll then use a custom `Open()` function which extracts the contents of the`_app` properties and applies them to corresponding properties of the form and its controls.
Update the form and controls to reflect preferences
`[Top|General Strategy]`

Now we move to the main form and our custom function called `Form.open()`.

| If you have not used a custom `form.open()` function yet in your coding, here’s why it is useful: A custom `form.open()` function is called before the FORM class’s built-in `Open()` function. This means that adjustments to the form occur before it is visible, thus eliminating obvious screen flicker as properties are changing. There’s one crucially important aspect of using an `Open()` function: you have to end it with this line:
| `return SUPER::Open()`
which calls the Form class’s own built-in `Open()` method. It is what actually opens the form. If you wish to do something analogous with a modal and SDI dialog form opened with `MyModalForm.readmodal()`, create a custom `form.readmodal()` function in the dialog and use it to modify properties of the dialog form. It will over-ride the built-in `Readmodal()` function belonging to the Form class. Your custom `form.readmodal()` function must end with this line:
| `return SUPER::Readmodal()`
which, as in the previous example, calls the Form class’s own built-in `Readmodal()` method. |

So, things are now getting simpler — all we need to do here is use our`_app` properties to change the relevant form and object properties as shown below:

| `Function Open` `// Here is where we use the values of our custom` `// _app properties to set form and object properties.` `// We do it here (particularly the form's coordinates)` `// rather than in the form's onOpen() so that the form` `// doesn't do funny stuff, like jerk around and blink.` `// set the EF1 entryfield's colorNormal` `form.EF1.colorNormal = _app.EF1color` `// set form's colorNormal` `form.colornormal = _app.formColor` `// set form's size/position` `form.height = val(_app.fheight)` `form.left = val(_app.fleft)` `form.top = val(_app.ftop)` `form.width = val(_app.fwidth)` `// Initialize a flag (as a custom property of the form)` `// to tell us if we need to update the ini file on exiting` `// the form. Better to use an _app property for this?` `// Your choice.` `form.DoIniUpdate = false` `// this next line is how you always end a custom` `// form.open() function:` `return SUPER::Open()` `// it calls the form Class's built-in Open()` `// function to actually open the form.`

We also initialize a custom property of the form (`form.DoIniUpdate`) that will be used as a flag to remind us if any changes are made to ini-related data.

Remember that numeric values are “character” in an ini file

At whatever point you grab numeric values from an ini file, always recall that if you wish to use them as numeric, you have to convert them by using the `VAL()` function. Here, since I chose to leave all the `_app` properties as character to match the ini file Values, we have to now do the conversion to numeric. You can see the four examples in the above code.

If you want to avoid using _app properties

As mentioned above, it is not necessary to use`_app` properties at all in this process. If we had decided to not use them, we could have left the header region code-free and instead used our `form.open()` function. That function is where we would instantiate the `oMyIni` object and directly alter the form and object properties, as shown (hypothetically!) below:

| `set proc to ini.cc additive` `oMyIni = new Ini(set("directory")+"\DemoIni.ini") // Instantiation` `if empty(oMyIni.Value) // no Value set, so set it` `oMyIni.SetValue("Preferences","EF1color","0xffb3ff/0xcc9999")` `form.EF1.colorNormal = "0xffb3ff/0xcc9999"` `else // otherwise, use the Value to set the object property contents` `form.EF1.colorNormal = oMyIni.Value` `endif`

Keep in mind that it would still be necessary to test for the existence of the ini file as we did in the header section. Using the “direct” method, we would save a fair bit of code by not having to initialize the `_app` properties and by avoiding the 6 lines of`_app-to-object.property` code in our `Open()` function. The other advantage to not using`_app` properties is that, if there are a lot of them, we save some RAM.

Done! The form is now open.

Check for Daily Tip

In the first few milliseconds after the form opens we have to do our first ini-specified task! We are going to use the form’s `onOpen()` event to check whether the user wants to see the “Daily Tip” form. If so, we’ll open that form as well. Here’s the form’s `onOpen()` code:

| `Function form_onOpen` `// make the code in these files available to use` `set proc to prefs.wfm additive` `set proc to dailytip.wfm additive` `// if we're supposed to display the Daily Tip form` `if _app.dailytip = true` `// declare a local var to hold the obj. reference to` `// the Daily Tip dialog form` `local dt` `// instantiate the form into the local variable` `dt = new dailytipFORM()` `// establish a communication "hot-line" back to the parent form` `dt.oParent = form` `// open the form "modal"...so, the code pauses until user` `// exits the dialog` `dt.readmodal()` `// now back from the form...zap the obj. reference to main form` `dt.oParent = null` `// release dialog form...not really necessary for a local var,` `// but hey...` `dt.release()` `// null it too...now we're *really* beating a dead horse...` `dt = null` `endif` `return`

The above is pretty standard code for opening a modal dialog box. We instantiate it into a local variable `dt`, set up a means to communicate with the parent via `dt.oParent`, and run the form. The user has the choice in this dialog form of either continuing to see a Daily Tip or not. If the user chooses to turn off the feature, the `dt` form tells the parent in this way:

| `// This is the only relevant code from the dialog form,` `// and is the onChange() event code for a checkbox that allows` `// the user to turn on or off the Daily Tip option.` `Function DAILYTIP_CB_onChange` `// set the _app property according to the value (T/F) of the checkbox` `_app.dailytip = iif(this.value = true, "true", "false")` `// set this parental flag to true so we update ini file on exit` `form.oParent.DoIniUpdate = true` `return`

Note: Here we must convert from the logical format of the checkbox value to the character format of the`_app` property… hence the`"true"` and`"false"` rather than `true` and `false`.

And, if the`_app.dailytip` property gets set to`"false"` and is not changed back to`"true"` before the program is exited, the ini file will be updated accordingly, and the next time the program is run, the Daily Tip will not be displayed.
Provide a means to specify preferences and update _app properties and controls
`[Top|General Strategy]`
Change the entryfield colors

On the `DemoIni` form is a pair of pushbuttons which allow the user to specify the text color and background colors of an entryfield.

While you may not wish to provide users with this particular option, the techniques described below can be applied to any control with a foreground/background `colorNormal` property.

| `Function EFTXTCOL_PB_onClick` `// use the getcolor() function to return an RGB color triplet` `cColor = getcolor("Pick a color for the text")` `// if a color was picked by the user` `if not empty(cColor)` `// shorten the reference to the colorNormal property` `cn = form.EF1.colornormal` `// change the colorNormal property to the new text color` `// and the existing background color` `form.EF1.colornormal := form.rgbToHex(cColor) + substr(cn,at("/",cn),20)` `// grab the entire colorNormal into its _app property` `_app.EF1color = form.EF1.colornormal` `// set the DoIniUpdate flag to true so we update the ini file on exit` `form.DoIniUpdate := true` `endif` `return` `Function EFBGCOL_PB_onClick` `// use the getcolor() function to return an RGB color triplet` `cColor = getcolor("Pick a color for the background")` `// a color was picked by the user` `if not empty(cColor)` `// shorten the reference to the colorNormal property` `cn = form.EF1.colornormal` `// change the colorNormal property to the existing text color` `// and the new background color` `form.EF1.colornormal = substr(cn,1,at("/",cn)) + form.rgbToHex(cColor)` `// grab the entire colorNormal into its _app property` `_app.EF1color := form.EF1.colornormal` `// set the DoIniUpdate flag to true so we update the ini file on exit` `form.DoIniUpdate = true` `endif` `return`

| The function called `form.RGBtoHex()` is a sweetheart written by Jay Parsons years ago, and is included in the dUFLP collection as part of Convert.cc It is fed an RGB (RedGreenBlue) triplet such as `175,175,216` and returns the hex equivalent:`0xD8AFAF` While we’re on the subject, the `0x` is not part of the hex numbers — it merely specifies that it is followed by hex numbers. |

Change form coordinates

The current coordinates of the form change, of course, as the form is moved or sized by the user. What we need to do is notice whether these changes occur. Below is the code that checks whether movement and/or sizing has occurred. As you can see, this is accomplished using codeblocks specified in the form’s constructor code. A codeblock runs if the form is moved or sized, and it sets our custom property `form.DoIniUpdate` to `true`:

| `class demoiniForm of FORM` `with (this)` `onMove = {;form.DoIniUpdate := true}` `onSize = {;form.DoIniUpdate := true}` `onOpen = class::FORM_ONOPEN` `onClose = class::FORM_ONCLOSE` `open = class::OPEN` `height = 10.6364` `left = 22.2857` `top = 5.9091` `width = 58.2857` `text = "Ini Ins and Outs"` `endwith`

If `form.DoIniUpdate` is `true` at exit time, the form’s coordinates are extracted and saved to the ini file.
The Preferences Dialog Box

Another means by which our sample form allows the user to specify two preferences is to use the standard approach — a Preferences dialog box. The idea is simple — temporarily save any changed user preferences; then, if the OK pushbutton is clicked, set the`_app` properties accordingly and change any form or object properties specified by the user.
Specify the main form’s color

Run the Preferences form from the main form to see how it behaves, then examine the code below which gets the chosen form color from the user, translates the RGB to a more usable format (via the `RGBtoHex()` function located on the parent form), then stores it in an object property:

| `Function CHOOSECOL_PB_onClick` `// user can pick a color from the color chart` `cFormCol = getcolor("Pick a color for the form")` `// a color was picked (in the RGB format, e.g., 175,175,216)` `if not empty(cFormCol)` `// switch RGB result to hex format (call parent function` `// using the obj.ref. oParent)` `cFormCol = form.oParent.rgbToHex(cFormCol) // returns a value like this: 0xD8AFAF` `// change the shape's color to display the new color` `form.nb1.FORMCOL_SHAPE.colorNormal = "black/" + cFormCol` `endif` `return`

The chosen color (e.g.,`0xD8AFAF`) is “stored” as part of the `colorNormal` property of a Shape object, which both illustrates the color for the user and saves the color value in a format that we can use if the user accepts the changes. Thus, the Shape’s `colorNormal` property might contain something like this:`"black/0xD8AFAF"`
Specify the DEO folder

We allow the user to specify the DEO folder as follows:

| `Function GETDEOFOLDER_PB_onClick` `// user chooses a folder` `cFolder = getdirectory()` `if not empty(cFolder)` `// set the folder name to the Entryfield value` `form.nb1.DEOFOLDER_EF.value = cFolder` `endif` `return`

dBASE’s `getDirectory()` function returns the drive:\path\folder name, so this is easy… and we “save” it temporarily by displaying it in the entryfield as the entryfield’s `value` property.
Accept or Cancel

As usual with a Preferences dialog, the user can either accept or cancel the changes. If changes are cancelled, we just close the form and that’s it. But if the user accepts the changes, we have to do two things:

1. Save the changed specified preferences to their respective`_app` properties, and
2. Update the form/object properties as specified by the user.

These actions take place in the Preferences form’s `OK_PB_onClick()` method:

| `Function OK_PB_onClick` `// shorten the reference to the colorNormal property` `cn = form.nb1.FORMCOL_SHAPE.colorNormal` `// get the last section of the Shape's colorNormal` `// for the main form's color _app property` `_app.formColor = substr(cn,at("/",cn)+1,15)` `// now go ahead and change main form's color` `form.oParent.colorNormal = _app.formColor` `// get the deo folder into its _app property` `_app.deoloc = trim(form.nb1.DEOFOLDER_EF.value)` `// update the DoIniUpdate flag` `form.DoIniUpdate = true` `// close the form` `form.close()` `return`

At this point, the form’s color has been changed to reflect the user’s choice, and the DEO folder has been stored in its`_app` property, ready to update the ini file when the user exits.

For the non-_app users

Again, if you prefer to not use`_app` properties, the above code would need to include, in place of the`_app` updates, instantiation of an `oMyIni` object from `INI.CC` and an update of the Values for `deoLoc` and `formColor` via the `SetValue()` function. The code for this is in the section below.
Store preferences in the ini file on program exit
`[Top|General Strategy]`

When the user exits the program, we can take the opportunity to update the ini file if needed. Below is the form’s `onClose()` event code where we first check our custom logical property form.DoIniUpdate to see if we need to update the ini file:

| `Function form_onClose` `// Write _app properties to the ini file if needed...` `if form.DoIniUpdate = true` `form.UpdateIni()` `endif` `return`

If we do need to update the ini file, the function `form.updateIni()` is called. Its code is below:

| `Function UpdateIni` `// Here is where we update the ini file before` `// exiting the app.` `// create (instantiate) INI object` `oMyIni = new Ini(set("directory")+"\DemoIni.ini")` `// entryfield colorNormal` `oMyIni.SetValue("Preferences","EF1color",_app.EF1color)` `// Form color` `oMyIni.SetValue("Preferences","formColor",_app.formColor)` `// DEO folder location` `oMyIni.SetValue("Preferences","deoLoc",_app.deoLoc)` `// daily tip` `oMyIni.SetValue("Preferences","dailytip",_app.dailytip)` `// form position/size coordinates` `cAllcoords = ;` `form.height + "," + form.left + "," + form.top + "," + form.width` `oMyIni.SetValue("Preferences","fcoords",cAllcoords)` `// force a write of the ini file to disk` `oMyIni.Flush()` `// release INI object` `oMyIni.release()` `return`

At this point, you’ll probably be able to figure out most of what’s going on in this function. We create a new `INI` object, then use the object’s `SetValue()` function to update the Entries in the ini file. We get the Values directly from our`_app` properties.

The only slightly tricky aspect of this is to concatenate, in the correct sequence, all four of the main form’s coordinates into a comma-separated string: we’re basically just adding a “,” between each coordinate. Notice that they do not have to be expressly converted to character — dBASE’s “Automatic Type Conversion” does that for us when we concatenate them to character elements (the commas). They are all thus saved as a string as the Value of the `fcoords` Entry.

Also notice in this `UpdateIni()` function that we are not testing any of our individual ini Values to see if they were changed. The approach here is basically this: if even one Value is changed during program execution, we’ll update all of the Values, changed or not. The overhead is low, so why not?

Done!
Conclusion
`[Top|General Strategy]`

This article has presented a working approach to managing ini files by use of the custom class `INI.CC` and custom`_app` properties, and has also provided some direction in accomplishing the same ends without the use of `_app` properties. We thus have the capacity to create from scratch a new ini file with an appropriate Section and its component Entries and Values; to repair an ini file that is missing the Section or any of its components; to extract Values from an ini file on loading an app; and to save changed data back to the ini file on exit from the program.

I hope that the categories of ini-storable data used here are sufficient to cover most of the sorts of data a user might wish to retain: object colors, a data location, whether to show a daily tip, and form coordinates. These settings include character, logical, and numeric data.

Goodies in INI.CC that I didn’t cover

Two of the functions,`DeleteSection()` and `DeleteEntry()` are very easy to figure out. The function called `SetIniFile()` can be used to create a new ini file, but we used the instantiation of the `INI` object to do the same thing. The pair of related functions called `GetSection()` and `SectionArrayParse2Arrays()` and their several related array properties are beyond the needs of most ordinary INI file management, but be sure to have a look at the descriptions of these functions in `INI.CC` so you can understand their potential.

Don’t think only in terms of MyApp.ini

Please don’t consider that the ini file named after an application’s EXE file is the only one that is editable from within the application. Any of your applications can have as many ini files as it needs in order to store/retrieve information. The only one it must have is the one named after the exe file, and that one will be automatically generated. And,`Win.ini` or any other ini file on your system is now within your reach as well.

Don’t think only in terms of ini files!

Heck, we’re database mavens! We’re sitting on one of the most powerful desktop database packages in the world. Who needs ini files? We certainly don’t — we can easily create an encrypted Preferences table that would be sufficiently difficult to penetrate that it would make hacking the Registry look boringly simple. Still, ini files have their uses, and they are a part of the Windows universe. A programmer should possess as many tools as possible.

Finally, as mentioned previously, the Registry is made quite approachable by `Registry.prg`, and all of the ini-related data storage/retrieval described in this article can easily be replaced by analogous Registry actions. But that’s another article.

Thanks to Keith Chuvala, Bowen Moursund, and Kenneth Chan for `INI.CC`; to Vic McClung for `Tokenize()`; to Jay Parsons for `RGBtoHex()`; and to Ken Mayer for adding `RGBtoHex()`to (and maintaining!) the dUFLP.

Many thanks also to Barbara Coultry for her first-rate editing of this article which substantially improved its readability and sniffed out many errors. I would also like to mention that I have decided to not act on her correct advice about the placement of certain periods within quotation marks and instead retain a syntax that is, I think, both more rational and more appealing to a programmer’s mentality. So, when you see the incorrect

“[Toolbars]” is referred to in ini-speak as a “Section”.
instead of the correct

“[Toolbars]” is referred to in ini-speak as a “Section.”

blame the error on me.