![]() |
|
Published 1999-07-27 Printer-friendly version
My very deep appreciation to Nik Johnson who bailed me out of control file purgatory (but you know what I really meant) when making the transition to ABC. The solution is his; the text of our exchange is at the end of this article.
I've been using control files since... I can't remember when. When I began moving to ABC, I was confident. My experiences with the app converter had been by and large quite good. But the code that accessed and maintained my control files didn't work after conversion.
Consulting Richard Taylor's superb "Making the Transition to ABC" in the online help, I was able to make some adjustments to what the converter had done. Still, it didn't work.
Control files, clearly, had become a different breed of cat in ABC.
A control file has two unique characteristics. First, a control file has only one record. Second, a control file has no keys (with only one record, a key is sort of pointless).
In many respects, control files duplicate the function of INI files. The important difference is that a control file, unless you use the ASCII or Basic driver (bad move), cannot be read with a text editor, is not so easily corrupted or manipulated as a text file and, should you so wish, can be encrypted. All or part of a standard file, which is what a control file is, can be made read-only.
The purposes for which you use or, indeed, whether you use a control file as opposed to an INI file is, of course, entirely up to you. But if you decide use a control file, how you use it is not.
Just as with any file, there two kinds of things you will want to do. First, you'll want to write to the control file, adding and updating records... oops, the record. When adding, of course, you'll want to add only to an empty file. Adding a second record to a control file defeats its purpose. So, adding is a "one time thing."
Second, you'll want access the record, i.e., read the file. Because a control file has only one record, by definition, and no keys, none of the standard template methods of file handling will work as expected. Indeed, they will not work at all.
Sequential processing is not possible (no "sequence," you see).
There is no key to prime. Set(key) and
Set(key,key) have nothing to operate on. A loop, of
course, is pointless. In plain English, the standard templates can
do nothing for you except open and close the file.
Similarly, you can't simply add records to the file. A control file may have one and no more than one record. The standard templates will try to add multiple records.
The problem is determining (1) whether or not the file exists at
runtime, (2) whether or not there is in fact a record in the file
and (3) how to tell a file what you want to do (Add or
Put).
(1) is usually only a problem the first time the app is run.
To make matters more interesting, the database driver that you
use makes a difference in accessing single record files. TPS files,
in particular, do not support the Pointer() function
for direct record retrieval while most other non-SQL databases do.
Your choice of file systems will make a difference in your handling
of control files.
For the moment, let's assume that there is already a record in the control file.
If you want to display the user's company name and address in the header of a report or plug the city, state and postal code on data entry form, the data contained in a record must have been successfully read first. If you haven't first read the record:
CUS:City = CFG:City
isn't likely to give the desired result, is it?
In CW 2.0, or even in DOS, you would done something like the following:
Open (file)
Get (file, 1)
Take a moment to look at this code.
First, the file is opened. This does nothing but create a record buffer in memory. Nothing here prepares the file to be read. Ok, it's not "nothing."
Second, normal file handling would follow with a
Next() or Set()/Next(). But a control
file contains only one record. If the driver fully supports
Pointer(), the record can be accessed directly,
Get(file,1)
The lesson? (1) Open, (2) read. Read = retain for later use.
TPS files don't fully support the Pointer()
function (on a TPS file, Pointer() returns a valid
pointer which can be used for direct retrieval but "1" is not a
valid pointer with TPS files), so:
Open(Config,42h) !or Share()
Set(Config)
Next(Config)
is required for TPS files.
Legacy command such as Open() and
Get() can in fact be used in these circumstances even
in ABC. Bad form, to be sure. But they will work. You must
ensure that the file is closed at the appropriate point, if you
Open the file directly.
In ABC, "good form" would be:
Access:Config.Open
Set(Config)
Access:Config.Next()
for TPS files.
Access:file.Open opens the file. Set()
prepares the file for reading, as always. And
Access:file.Next() reads the first (and in this
case only) record, if any.
You will notice there is no ABC analog for the
Get() command. Therefore, for file systems fully
supporting Pointer(), you can continue using
Get():
Access:Config.Open
Get(Config,1)
With these file systems, xBase, Clarion, etc., I have had the
Set()/Next() strategy fail. Thus, I continue using
Get().
Since there is only one record, a browse is sort of... ah, pointless. Go directly to the form.
The problem is that a form needs to be told what to do.
GlobalRequest is typically used to tell the form
whether it is being call to add, update or delete a record. Without
a legitimate value in GlobalRequest, the form
won't do anything.
Specifically, if called to add a new record, the form needs an empty buffer. If called to change or delete, the form needs a buffer with the correct record.
So, GlobalRequest needs to be set; what's the
big deal? The big deal is that you need to know whether or not the
file has a record before you set GlobalRequest.
If there is no record, GlobalRequest must be
InsertRecord and if there is a record
GlobalRequest must be ChangeRecord
(you don't ever want to delete a control record, do you?).
As it turns out, this is easily determined. If
Relate:file.Open returns a non-zero value,
there was an error opening the file (e.g., the file does not exist
and is not set up for create-if-not-found). If
Access:file.Next() returns a value, there is no
record in the file. So the following code can be used to set up the
call to a form:
IF NOT Relate:Config.Open()
SET(Config)
IF Access:Config.Next()
GlobalRequest = InsertRecord
ELSE
GlobalRequest = ChangeRecord
END
SetupForm
Relate:Config.Close
Else
!action if file create not allowed
END
This ensures that GlobalRequest is always set properly.
Trying to determine the appropriate value for GlobalRequest from inside the Form procedure is a bit more difficult. It is more difficult because GlobalRequest is read before the file is opened (see Figure 1), early in the INIT method.

Of course, the code in Listing 1 will work perfectly well before
"Snap-shot GlobalRequest" (without the procedure call,
of course) and not wreck havoc on the standard template code (so
long as you close the file first).
To ensure that the configuration file is properly opened and read when the program is started, I use something like:
Loop
Access:Config.Open
Set(Config)
If Access:Config.Next()
SetupForm
Else
Break
End
End
in the main procedure's INIT method. Notice
that this code loops until there is no problem opening and reading
the file. Combined with the code in Listing 1, the form always
knows what is required of it.
Config files aren't especially difficult. But they do prove just how spoiled we are. Everywhere else, Clarion sets up the file buffer, accesses files and sets up forms. When there's only one record in the file, you're on your own as far as these go.
On the other hand, if you can manipulate a configuration file, you know how to access a file.
In next week's issue of Clarion Magazine, Nik Johnson takes control files a step further with a class and template.
> I have a single record (configuration) file and, based on what
> I did in CW2003, I used:
>
> Access:Config.Open
> Set(Config)
> Access:Config.Next()
>
> to get that record, but it doesn't seem to be returning the
> correct >field values.Could it be that the error is in my file
> access in the update form:
>
> Access:Config.Next()
> If ErrorCode()
> If ErrorCode() = 35 then ThisWindow.Request = InsertRecord.
> Else
> ThisWindow.Request = ChangeRecord
> End
> ThisWindow.OriginalRequest = ThisWindow.Request
>
> because I noticed (finally) that I had 15 or 16 records in the
> file. Ok, where'd I foul up? (Please...)
>
> TIA
>
> Steve Parker
(Nik Johnson's reply:)
I think you have to be careful about timing here. What I would
do is have a source procedure which sits between the menu (or
whatever triggers the configuration file update process) and
the update form:
LinkUpdate PROCEDURE
CODE
IF NOT Relate:Config.Open
SET(Config)
IF Access:Config.Next()
GlobalRequest = InsertRecord
ELSE
GlobalRequest = ChangeRecord
END
ConfigForm()
Relate:Config.Close
END
RETURN
This makes sure that the file is open, the record is current
or the buffer cleared) and GlobalRequest is set -before- you
ever get to the update form. In that way you don't have to
worry about little things like the fact that the update
window will internalize GlobalRequest before -it- opens the file.
The update form at this point should see no difference between
this call for a singular record and a request from a browse of
many records. Objects are like people. They function best when
given directions in a familiar context.
-Nik
Steve
Parker started his professional life as a Philosopher but now tries to imitate
a Clarion developer. He has been attempting to subdue Clarion since version 2007 (DOS,
that is). He reports that, so far, Clarion is winning. Steve has been writing
about Clarion since 1993.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239